@remnic/core 10.0.49 → 10.0.51

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 (415) hide show
  1. package/dist/access-admin-ops-surface.js +49 -48
  2. package/dist/access-authorization-probe.js +50 -49
  3. package/dist/access-boundary.d.ts +6 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +49 -48
  6. package/dist/access-cli.js +120 -119
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-errors.d.ts.map +1 -1
  9. package/dist/access-errors.js +1 -1
  10. package/dist/access-extraction-force-flush.js +49 -48
  11. package/dist/access-http-briefing.d.ts +2 -0
  12. package/dist/access-http-briefing.d.ts.map +1 -1
  13. package/dist/access-http-briefing.js +50 -49
  14. package/dist/access-http-query.js +51 -50
  15. package/dist/access-http.d.ts.map +1 -1
  16. package/dist/access-http.js +70 -69
  17. package/dist/access-identity-continuity-surface.js +49 -48
  18. package/dist/access-lcm-surface.js +49 -48
  19. package/dist/access-mcp.js +60 -59
  20. package/dist/access-memory-search-fanout.js +2 -2
  21. package/dist/access-namespace-preflight.js +49 -48
  22. package/dist/access-observe-write-category.d.ts +1 -1
  23. package/dist/access-observe-write-category.d.ts.map +1 -1
  24. package/dist/access-observe-write-category.js +10 -10
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +49 -48
  27. package/dist/access-offline-manifest.js +6 -6
  28. package/dist/access-operations-batch.js +50 -49
  29. package/dist/access-operations.js +57 -56
  30. package/dist/access-recall-concurrency.js +49 -48
  31. package/dist/access-recall-diagnostics.js +2 -2
  32. package/dist/access-recall-response.js +49 -48
  33. package/dist/access-recall-surface.js +49 -48
  34. package/dist/access-schema.d.ts +245 -773
  35. package/dist/access-schema.d.ts.map +1 -1
  36. package/dist/access-schema.js +6 -6
  37. package/dist/access-service.d.ts +5 -0
  38. package/dist/access-service.d.ts.map +1 -1
  39. package/dist/access-service.js +49 -48
  40. package/dist/access-surface-catalog.js +51 -50
  41. package/dist/access-surface-catalog.js.map +1 -1
  42. package/dist/access-token-capabilities.js +2 -2
  43. package/dist/active-recall.js +16 -16
  44. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  45. package/dist/briefing-deadline.d.ts +7 -0
  46. package/dist/briefing-deadline.d.ts.map +1 -0
  47. package/dist/briefing-deadline.js +13 -0
  48. package/dist/briefing-window.d.ts +12 -4
  49. package/dist/briefing-window.d.ts.map +1 -1
  50. package/dist/briefing-window.js +3 -1
  51. package/dist/briefing.d.ts +2 -0
  52. package/dist/briefing.d.ts.map +1 -1
  53. package/dist/briefing.js +12 -11
  54. package/dist/buffer.js +3 -3
  55. package/dist/calibration.js +2 -2
  56. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  57. package/dist/causal-consolidation.js +11 -11
  58. package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
  59. package/dist/{chunk-BWF3KNSQ.js → chunk-2EXF6IIB.js} +2 -2
  60. package/dist/{chunk-AGCEJDG2.js → chunk-2ONKXEC7.js} +3 -3
  61. package/dist/{chunk-3NYIXVCT.js → chunk-2S6JADBB.js} +2 -2
  62. package/dist/chunk-2S6JADBB.js.map +1 -0
  63. package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
  64. package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
  65. package/dist/{chunk-Q6PYCPHO.js → chunk-44EUCSG4.js} +2 -2
  66. package/dist/{chunk-2J6BIOVN.js → chunk-4FM35YIA.js} +3 -3
  67. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  68. package/dist/{chunk-YY6PWFUC.js → chunk-6ZAU7LAR.js} +3 -3
  69. package/dist/{chunk-HCPVL6SF.js → chunk-72CBJ5VP.js} +3 -3
  70. package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
  71. package/dist/{chunk-N6EM7ESC.js → chunk-75BK4XGV.js} +27 -19
  72. package/dist/chunk-75BK4XGV.js.map +1 -0
  73. package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
  74. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  75. package/dist/chunk-A5L5KGDI.js.map +1 -0
  76. package/dist/{chunk-RZDDNH63.js → chunk-A6JDT6QF.js} +27 -15
  77. package/dist/chunk-A6JDT6QF.js.map +1 -0
  78. package/dist/{chunk-XFND63OB.js → chunk-AHGOOZUC.js} +2 -2
  79. package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
  80. package/dist/{chunk-2BY56HE6.js → chunk-AZLD4CKG.js} +36 -36
  81. package/dist/{chunk-E47UQBYW.js → chunk-B35EQQ7L.js} +3 -3
  82. package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
  83. package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
  84. package/dist/{chunk-SVJ5JVZO.js → chunk-CK6GCGMX.js} +4 -4
  85. package/dist/{chunk-QZK6HG4J.js → chunk-DF6ZF2BN.js} +3 -3
  86. package/dist/{chunk-CPBYKOTC.js → chunk-DJFULNXK.js} +4 -4
  87. package/dist/{chunk-VGRBKEBH.js → chunk-DQOGZZJA.js} +3 -3
  88. package/dist/{chunk-YB3WN6XA.js → chunk-EC2KVA27.js} +12 -12
  89. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  90. package/dist/chunk-EKUI7I2X.js.map +1 -0
  91. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  92. package/dist/{chunk-WIBWONL4.js → chunk-EZS6QJNC.js} +2 -2
  93. package/dist/{chunk-C7WDWD35.js → chunk-FNNGWN3O.js} +5 -5
  94. package/dist/{chunk-PBWJVRKE.js → chunk-FT75AQ74.js} +3 -3
  95. package/dist/{chunk-VY6BD5KB.js → chunk-G6A3SGCE.js} +14 -12
  96. package/dist/chunk-G6A3SGCE.js.map +1 -0
  97. package/dist/{chunk-O4CA53UY.js → chunk-GBGU5OZP.js} +111 -104
  98. package/dist/chunk-GBGU5OZP.js.map +1 -0
  99. package/dist/{chunk-WJCAAVZX.js → chunk-GE3ZX6YK.js} +2 -2
  100. package/dist/{chunk-3PVKV3XL.js → chunk-HCIRR3BA.js} +3 -3
  101. package/dist/{chunk-BJTFLP3J.js → chunk-HEF4BRMT.js} +3 -3
  102. package/dist/{chunk-BMG3SAXA.js → chunk-HQUYENKK.js} +9 -12
  103. package/dist/chunk-HQUYENKK.js.map +1 -0
  104. package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
  105. package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
  106. package/dist/{chunk-3INLTKTW.js → chunk-IUQXRRJB.js} +4 -4
  107. package/dist/{chunk-6W3QH3QW.js → chunk-JIJAWTRK.js} +388 -292
  108. package/dist/chunk-JIJAWTRK.js.map +1 -0
  109. package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
  110. package/dist/{chunk-SW3WHVKN.js → chunk-KAJYSO3U.js} +2 -2
  111. package/dist/{chunk-D76AHITY.js → chunk-KETS5A62.js} +2 -2
  112. package/dist/{chunk-LCHFINL4.js → chunk-KIWO4327.js} +2 -2
  113. package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
  114. package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
  115. package/dist/{chunk-6TIVVOVU.js → chunk-L3EFYRYK.js} +9 -9
  116. package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
  117. package/dist/chunk-LGEH473S.js.map +1 -0
  118. package/dist/{chunk-X52SXKVC.js → chunk-LMB47UM6.js} +2 -2
  119. package/dist/{chunk-A3WXV7YU.js → chunk-LWMLBVGB.js} +2 -2
  120. package/dist/{chunk-KMTMMBEB.js → chunk-MC7PXJDP.js} +7 -7
  121. package/dist/{chunk-NMOEOQV4.js → chunk-MUNYUSBU.js} +2 -2
  122. package/dist/{chunk-VKUMKVEU.js → chunk-OCRLJJOV.js} +5 -5
  123. package/dist/{chunk-XSJL62LE.js → chunk-ON77FQXH.js} +2 -2
  124. package/dist/{chunk-C3HRYVGD.js → chunk-POJRFSJM.js} +3 -3
  125. package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
  126. package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
  127. package/dist/chunk-QT5MGENX.js.map +1 -0
  128. package/dist/{chunk-QFTNWDJ6.js → chunk-RA7YRL6H.js} +2 -2
  129. package/dist/{chunk-WQFHHJWK.js → chunk-RJDYDF5K.js} +4 -4
  130. package/dist/{chunk-E3DL3CAL.js → chunk-ROAKLYOL.js} +2 -2
  131. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  132. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  133. package/dist/{chunk-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
  134. package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
  135. package/dist/{chunk-OD7ZXCUF.js → chunk-SFRBUBUZ.js} +10 -10
  136. package/dist/{chunk-7XFQSQYS.js → chunk-SHNM7RIT.js} +5 -4
  137. package/dist/chunk-SHNM7RIT.js.map +1 -0
  138. package/dist/{chunk-NH37SP6V.js → chunk-SJD334AG.js} +81 -78
  139. package/dist/chunk-SJD334AG.js.map +1 -0
  140. package/dist/{chunk-O7GNSHFF.js → chunk-SPTPBKZX.js} +2 -2
  141. package/dist/{chunk-HI53EK3Y.js → chunk-TC55M5PO.js} +2 -2
  142. package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
  143. package/dist/chunk-TEH5MDX5.js.map +1 -0
  144. package/dist/{chunk-FOKTW6GY.js → chunk-UCLJTGP3.js} +2 -2
  145. package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
  146. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  147. package/dist/chunk-UWMZT2TY.js.map +1 -0
  148. package/dist/{chunk-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
  149. package/dist/{chunk-DM6SOROF.js → chunk-V6Z4NAJZ.js} +2 -2
  150. package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
  151. package/dist/{chunk-NUK5XU3P.js → chunk-VUMWVYMR.js} +28 -13
  152. package/dist/chunk-VUMWVYMR.js.map +1 -0
  153. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  154. package/dist/{chunk-VCBS5GRQ.js → chunk-WY66J4PI.js} +2 -2
  155. package/dist/chunk-X2KO4FK5.js +40 -0
  156. package/dist/chunk-X2KO4FK5.js.map +1 -0
  157. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  158. package/dist/{chunk-V4GPFCJ7.js → chunk-XWJUFER3.js} +3 -3
  159. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  160. package/dist/{chunk-CRJX4OMN.js → chunk-Y3T5KQCR.js} +2 -2
  161. package/dist/{chunk-KNCR7APE.js → chunk-Y77FXULH.js} +8 -8
  162. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  163. package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
  164. package/dist/{chunk-HDT3DMUF.js → chunk-ZKK3VMWU.js} +2 -2
  165. package/dist/cli.js +92 -91
  166. package/dist/coding/export-okf-codegraph.js +9 -9
  167. package/dist/compounding/engine.js +11 -11
  168. package/dist/config.js +16 -16
  169. package/dist/connectors/codex-materialize-runner.js +10 -10
  170. package/dist/connectors/index.js +14 -14
  171. package/dist/consolidation-provenance-check.js +4 -4
  172. package/dist/consolidation-undo.js +2 -2
  173. package/dist/convergence-refresh.js +12 -12
  174. package/dist/corpus-watermark.js +14 -14
  175. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  176. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  177. package/dist/dashboard-runtime.js +2 -2
  178. package/dist/deep-recall-config.js +2 -2
  179. package/dist/deep-recall-http-glue.js +9 -9
  180. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-F3JVG6K6.js} +3 -3
  181. package/dist/entity-canonical-id-migration-F3JVG6K6.js.map +1 -0
  182. package/dist/entity-retrieval.js +11 -11
  183. package/dist/explicit-capture.js +2 -2
  184. package/dist/external-wiki-access.js +50 -49
  185. package/dist/extraction-span-wire.d.ts.map +1 -1
  186. package/dist/extraction-span-wire.js +1 -1
  187. package/dist/extraction.js +22 -22
  188. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  189. package/dist/harmonic-retrieval.js +10 -10
  190. package/dist/index.d.ts +1 -0
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +143 -135
  193. package/dist/lcm/engine.js +2 -2
  194. package/dist/lcm/index.js +5 -5
  195. package/dist/local-llm.js +2 -2
  196. package/dist/location/index.js +16 -16
  197. package/dist/location/tagging.d.ts +7 -5
  198. package/dist/location/tagging.d.ts.map +1 -1
  199. package/dist/maintenance/memory-governance.js +10 -10
  200. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  201. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
  202. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  203. package/dist/memory-browse-http-glue.js +2 -2
  204. package/dist/memory-browse-operations.js +50 -49
  205. package/dist/namespaces/migrate.js +11 -11
  206. package/dist/namespaces/storage.js +10 -10
  207. package/dist/offline-sync-file-io.d.ts +15 -0
  208. package/dist/offline-sync-file-io.d.ts.map +1 -1
  209. package/dist/offline-sync-file-io.js +9 -1
  210. package/dist/offline-sync-impression-drain.js +2 -2
  211. package/dist/offline-sync-snapshot-stream.js +4 -4
  212. package/dist/offline-sync-upload-staging.js +2 -2
  213. package/dist/offline-sync.d.ts.map +1 -1
  214. package/dist/offline-sync.js +3 -3
  215. package/dist/operator-doctor-corpus.js +17 -17
  216. package/dist/operator-doctor-replica.js +4 -4
  217. package/dist/operator-toolkit.js +29 -29
  218. package/dist/orchestration/maintenance.js +25 -25
  219. package/dist/orchestrator.d.ts +1 -0
  220. package/dist/orchestrator.d.ts.map +1 -1
  221. package/dist/orchestrator.js +122 -121
  222. package/dist/page-versioning.js +2 -2
  223. package/dist/recall-navigation-http-glue.js +2 -2
  224. package/dist/recall-navigation-operations.js +50 -49
  225. package/dist/recall-navigation.js +49 -48
  226. package/dist/relay/mission.d.ts +731 -3403
  227. package/dist/relay/mission.d.ts.map +1 -1
  228. package/dist/replica-divergence.js +3 -3
  229. package/dist/resume-bundles.js +17 -17
  230. package/dist/schemas.d.ts +346 -1771
  231. package/dist/schemas.d.ts.map +1 -1
  232. package/dist/semantic-consolidation.js +15 -15
  233. package/dist/semantic-rule-promotion.js +10 -10
  234. package/dist/semantic-rule-verifier.js +10 -10
  235. package/dist/shared-context/manager.d.ts +13 -33
  236. package/dist/shared-context/manager.d.ts.map +1 -1
  237. package/dist/source-agent-qualifier.js +18 -18
  238. package/dist/standup/build.js +11 -11
  239. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  240. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  241. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  242. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  243. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  244. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  245. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  246. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  247. package/dist/storage/entity-store.d.ts +1 -8
  248. package/dist/storage/entity-store.d.ts.map +1 -1
  249. package/dist/storage/memory-read-store.d.ts +3 -2
  250. package/dist/storage/memory-read-store.d.ts.map +1 -1
  251. package/dist/storage/windowed-memory-read.d.ts +6 -0
  252. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  253. package/dist/storage.d.ts +2 -0
  254. package/dist/storage.d.ts.map +1 -1
  255. package/dist/storage.js +9 -9
  256. package/dist/summarizer.js +3 -3
  257. package/dist/support-passport/access-operations.d.ts +51 -51
  258. package/dist/support-passport/contracts.d.ts +85 -126
  259. package/dist/support-passport/contracts.d.ts.map +1 -1
  260. package/dist/support-passport/generated-batch.d.ts +3 -17
  261. package/dist/support-passport/generated-batch.d.ts.map +1 -1
  262. package/dist/support-passport/grant-contracts.d.ts +75 -381
  263. package/dist/support-passport/grant-contracts.d.ts.map +1 -1
  264. package/dist/support-passport/index.js +52 -51
  265. package/dist/support-passport/model-audit.d.ts +17 -82
  266. package/dist/support-passport/model-audit.d.ts.map +1 -1
  267. package/dist/support-passport/model-bridge.d.ts +12 -51
  268. package/dist/support-passport/model-bridge.d.ts.map +1 -1
  269. package/dist/support-passport/model-contracts.d.ts +53 -192
  270. package/dist/support-passport/model-contracts.d.ts.map +1 -1
  271. package/dist/tokens.js +3 -3
  272. package/dist/transfer/capsule-export.js +2 -2
  273. package/dist/transfer/capsule-import.js +4 -4
  274. package/dist/transfer/export-okf.js +10 -10
  275. package/dist/transfer/types.d.ts +32 -737
  276. package/dist/transfer/types.d.ts.map +1 -1
  277. package/dist/verified-recall.js +10 -10
  278. package/dist/who-knows-cli.js +12 -12
  279. package/dist/who-knows.js +11 -11
  280. package/package.json +3 -4
  281. package/src/access-boundary.test.ts +14 -0
  282. package/src/access-boundary.ts +48 -42
  283. package/src/access-category-alias.test.ts +9 -7
  284. package/src/access-errors.test.ts +21 -9
  285. package/src/access-errors.ts +45 -31
  286. package/src/access-http-briefing.ts +4 -0
  287. package/src/access-http.test.ts +41 -12
  288. package/src/access-http.ts +6 -0
  289. package/src/access-observe-write-category.ts +1 -1
  290. package/src/access-observe-write-surface.ts +24 -19
  291. package/src/access-operations-batch.ts +18 -2
  292. package/src/access-schema.ts +13 -17
  293. package/src/access-service.ts +5 -0
  294. package/src/briefing-abort.test.ts +173 -0
  295. package/src/briefing-deadline.test.ts +84 -0
  296. package/src/briefing-deadline.ts +33 -0
  297. package/src/briefing-window-abort.test.ts +47 -0
  298. package/src/briefing-window.ts +33 -4
  299. package/src/briefing.ts +23 -16
  300. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  301. package/src/correction/passive-correction-notifications.ts +4 -0
  302. package/src/entity-lazy-compatibility.test.ts +229 -0
  303. package/src/entity-migration-completion-notification.test.ts +148 -0
  304. package/src/extraction-scope-source-agent.test.ts +6 -6
  305. package/src/extraction-span-wire.test.ts +28 -0
  306. package/src/extraction-span-wire.ts +34 -6
  307. package/src/index.ts +1 -1
  308. package/src/location/tagging.ts +14 -12
  309. package/src/offline-sync-file-io.ts +61 -2
  310. package/src/offline-sync.test.ts +313 -20
  311. package/src/offline-sync.ts +43 -21
  312. package/src/orchestrator-init.test.ts +49 -1
  313. package/src/orchestrator.ts +9 -8
  314. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  315. package/src/storage/entity-canonical-id-migration.ts +19 -10
  316. package/src/storage/entity-canonical-id-references.ts +24 -0
  317. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  318. package/src/storage/entity-migration-completion-cache.ts +153 -0
  319. package/src/storage/entity-store.ts +58 -86
  320. package/src/storage/memory-read-store.ts +9 -3
  321. package/src/storage/windowed-memory-read.ts +8 -0
  322. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  323. package/src/storage.ts +7 -2
  324. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  325. package/dist/chunk-23ZLN5X6.js.map +0 -1
  326. package/dist/chunk-3MUKQQ7A.js.map +0 -1
  327. package/dist/chunk-3NYIXVCT.js.map +0 -1
  328. package/dist/chunk-6W3QH3QW.js.map +0 -1
  329. package/dist/chunk-7XFQSQYS.js.map +0 -1
  330. package/dist/chunk-BMG3SAXA.js.map +0 -1
  331. package/dist/chunk-FRXS6SC7.js.map +0 -1
  332. package/dist/chunk-LWRIGKFM.js.map +0 -1
  333. package/dist/chunk-N6EM7ESC.js.map +0 -1
  334. package/dist/chunk-NH37SP6V.js.map +0 -1
  335. package/dist/chunk-NUK5XU3P.js.map +0 -1
  336. package/dist/chunk-O4CA53UY.js.map +0 -1
  337. package/dist/chunk-RZDDNH63.js.map +0 -1
  338. package/dist/chunk-SA6P2KS3.js.map +0 -1
  339. package/dist/chunk-SUKPM6DV.js.map +0 -1
  340. package/dist/chunk-VY6BD5KB.js.map +0 -1
  341. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  342. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
  343. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  344. /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
  345. /package/dist/{chunk-BWF3KNSQ.js.map → chunk-2EXF6IIB.js.map} +0 -0
  346. /package/dist/{chunk-AGCEJDG2.js.map → chunk-2ONKXEC7.js.map} +0 -0
  347. /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
  348. /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
  349. /package/dist/{chunk-Q6PYCPHO.js.map → chunk-44EUCSG4.js.map} +0 -0
  350. /package/dist/{chunk-2J6BIOVN.js.map → chunk-4FM35YIA.js.map} +0 -0
  351. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  352. /package/dist/{chunk-YY6PWFUC.js.map → chunk-6ZAU7LAR.js.map} +0 -0
  353. /package/dist/{chunk-HCPVL6SF.js.map → chunk-72CBJ5VP.js.map} +0 -0
  354. /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
  355. /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
  356. /package/dist/{chunk-XFND63OB.js.map → chunk-AHGOOZUC.js.map} +0 -0
  357. /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
  358. /package/dist/{chunk-2BY56HE6.js.map → chunk-AZLD4CKG.js.map} +0 -0
  359. /package/dist/{chunk-E47UQBYW.js.map → chunk-B35EQQ7L.js.map} +0 -0
  360. /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
  361. /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
  362. /package/dist/{chunk-SVJ5JVZO.js.map → chunk-CK6GCGMX.js.map} +0 -0
  363. /package/dist/{chunk-QZK6HG4J.js.map → chunk-DF6ZF2BN.js.map} +0 -0
  364. /package/dist/{chunk-CPBYKOTC.js.map → chunk-DJFULNXK.js.map} +0 -0
  365. /package/dist/{chunk-VGRBKEBH.js.map → chunk-DQOGZZJA.js.map} +0 -0
  366. /package/dist/{chunk-YB3WN6XA.js.map → chunk-EC2KVA27.js.map} +0 -0
  367. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  368. /package/dist/{chunk-WIBWONL4.js.map → chunk-EZS6QJNC.js.map} +0 -0
  369. /package/dist/{chunk-C7WDWD35.js.map → chunk-FNNGWN3O.js.map} +0 -0
  370. /package/dist/{chunk-PBWJVRKE.js.map → chunk-FT75AQ74.js.map} +0 -0
  371. /package/dist/{chunk-WJCAAVZX.js.map → chunk-GE3ZX6YK.js.map} +0 -0
  372. /package/dist/{chunk-3PVKV3XL.js.map → chunk-HCIRR3BA.js.map} +0 -0
  373. /package/dist/{chunk-BJTFLP3J.js.map → chunk-HEF4BRMT.js.map} +0 -0
  374. /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
  375. /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
  376. /package/dist/{chunk-3INLTKTW.js.map → chunk-IUQXRRJB.js.map} +0 -0
  377. /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
  378. /package/dist/{chunk-SW3WHVKN.js.map → chunk-KAJYSO3U.js.map} +0 -0
  379. /package/dist/{chunk-D76AHITY.js.map → chunk-KETS5A62.js.map} +0 -0
  380. /package/dist/{chunk-LCHFINL4.js.map → chunk-KIWO4327.js.map} +0 -0
  381. /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
  382. /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
  383. /package/dist/{chunk-6TIVVOVU.js.map → chunk-L3EFYRYK.js.map} +0 -0
  384. /package/dist/{chunk-X52SXKVC.js.map → chunk-LMB47UM6.js.map} +0 -0
  385. /package/dist/{chunk-A3WXV7YU.js.map → chunk-LWMLBVGB.js.map} +0 -0
  386. /package/dist/{chunk-KMTMMBEB.js.map → chunk-MC7PXJDP.js.map} +0 -0
  387. /package/dist/{chunk-NMOEOQV4.js.map → chunk-MUNYUSBU.js.map} +0 -0
  388. /package/dist/{chunk-VKUMKVEU.js.map → chunk-OCRLJJOV.js.map} +0 -0
  389. /package/dist/{chunk-XSJL62LE.js.map → chunk-ON77FQXH.js.map} +0 -0
  390. /package/dist/{chunk-C3HRYVGD.js.map → chunk-POJRFSJM.js.map} +0 -0
  391. /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
  392. /package/dist/{chunk-QFTNWDJ6.js.map → chunk-RA7YRL6H.js.map} +0 -0
  393. /package/dist/{chunk-WQFHHJWK.js.map → chunk-RJDYDF5K.js.map} +0 -0
  394. /package/dist/{chunk-E3DL3CAL.js.map → chunk-ROAKLYOL.js.map} +0 -0
  395. /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
  396. /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
  397. /package/dist/{chunk-OD7ZXCUF.js.map → chunk-SFRBUBUZ.js.map} +0 -0
  398. /package/dist/{chunk-O7GNSHFF.js.map → chunk-SPTPBKZX.js.map} +0 -0
  399. /package/dist/{chunk-HI53EK3Y.js.map → chunk-TC55M5PO.js.map} +0 -0
  400. /package/dist/{chunk-FOKTW6GY.js.map → chunk-UCLJTGP3.js.map} +0 -0
  401. /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
  402. /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
  403. /package/dist/{chunk-DM6SOROF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
  404. /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
  405. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  406. /package/dist/{chunk-VCBS5GRQ.js.map → chunk-WY66J4PI.js.map} +0 -0
  407. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  408. /package/dist/{chunk-V4GPFCJ7.js.map → chunk-XWJUFER3.js.map} +0 -0
  409. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  410. /package/dist/{chunk-CRJX4OMN.js.map → chunk-Y3T5KQCR.js.map} +0 -0
  411. /package/dist/{chunk-KNCR7APE.js.map → chunk-Y77FXULH.js.map} +0 -0
  412. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  413. /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
  414. /package/dist/{chunk-HDT3DMUF.js.map → chunk-ZKK3VMWU.js.map} +0 -0
  415. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -70,6 +70,10 @@ import {
70
70
  setSafeFileMtime,
71
71
  assertNonNegativeFinite,
72
72
  assertOfflineSyncMtimeMs,
73
+ offlineSyncRegularFilePresent,
74
+ OfflineSyncCensusRaceError,
75
+ censusRaceAsChangedWhileBuilding,
76
+ changedWhileBuildingChangesetError,
73
77
  OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS,
74
78
  } from "./offline-sync-file-io.js";
75
79
  import { CENSUS_MAX_MTIME_MS, isSha256Hex } from "./census-validation.js";
@@ -793,19 +797,29 @@ async function canReuseFastBaseFileStateFromDisk(
793
797
 
794
798
  async function readOfflineSyncFileRecord(
795
799
  options: OfflineSyncFileRecordOptions,
796
- ): Promise<OfflineSyncFileRecord | null> {
800
+ ): Promise<OfflineSyncFileRecord> {
797
801
  try {
798
802
  return await readPresentOfflineSyncFileRecord(options);
799
803
  } catch (error) {
800
- // A file removed between readdir and stat/hash is a race, not a corrupt corpus.
801
- if (isNotFoundError(error)) return null;
804
+ // Caller already observed the path (readdir/lstat). Digest ENOENT is an
805
+ // ambiguous race: exporting a silent omission assigns confirmed absence to
806
+ // every consumer (full snapshot apply + changeset apply). Fail closed.
807
+ if (isNotFoundError(error)) {
808
+ if (embeddingGenerationMembership(options.relPath) !== null) {
809
+ throw new Error(
810
+ `offline sync embedding generation member vanished during snapshot walk: ${options.relPath}`,
811
+ );
812
+ }
813
+ throw new OfflineSyncCensusRaceError(options.relPath, error);
814
+ }
802
815
  throw error;
803
816
  }
804
817
  }
805
818
 
806
- // A vanished path may only be omitted when nothing can misread the gap:
807
- // embedding generations travel and replace as one unit (issue #3148), and a
808
- // base-known file omitted here would fabricate a delete in
819
+
820
+ // Pre-stat vanish (readdir then lstat/stat ENOENT) may omit only when nothing
821
+ // can misread the gap: embedding generations travel as one unit (issue #3148),
822
+ // and a base-known file omitted here would fabricate a delete in
809
823
  // buildOfflineSyncChangesetFromSnapshot.
810
824
  function assertSnapshotVanishIsSafe(relPath: string, base: ReadonlyMap<string, unknown> | undefined): void {
811
825
  if (embeddingGenerationMembership(relPath) !== null) {
@@ -816,6 +830,7 @@ function assertSnapshotVanishIsSafe(relPath: string, base: ReadonlyMap<string, u
816
830
  }
817
831
  }
818
832
 
833
+
819
834
  async function readPresentOfflineSyncFileRecord(
820
835
  options: OfflineSyncFileRecordOptions,
821
836
  ): Promise<OfflineSyncFileRecord> {
@@ -923,7 +938,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
923
938
  path: relPosix,
924
939
  filePath: abs,
925
940
  })) continue;
926
- const record = await readOfflineSyncFileRecord({
941
+ yield await readOfflineSyncFileRecord({
927
942
  root,
928
943
  relPath: relPosix,
929
944
  filePath: abs,
@@ -932,8 +947,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
932
947
  readFileDigest: options.readFileDigest,
933
948
  signal: options.signal,
934
949
  });
935
- if (record) yield record;
936
- else assertSnapshotVanishIsSafe(relPosix, undefined);
950
+
937
951
  }
938
952
  }
939
953
 
@@ -1094,8 +1108,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1094
1108
  try {
1095
1109
  st = await stat(abs);
1096
1110
  } catch (error) {
1097
- // Same race readOfflineSyncFileRecord tolerates, unless omitting the
1098
- // path could fabricate a delete or downgrade a generation.
1111
+ // Pre-stat vanish after readdir: omit only when assertSnapshotVanishIsSafe allows.
1099
1112
  if (isNotFoundError(error)) {
1100
1113
  assertSnapshotVanishIsSafe(relPosix, base);
1101
1114
  continue;
@@ -1112,7 +1125,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1112
1125
  files.push(baseEntry);
1113
1126
  continue;
1114
1127
  }
1115
- const record = await readOfflineSyncFileRecord({
1128
+ files.push(await readOfflineSyncFileRecord({
1116
1129
  root,
1117
1130
  relPath: relPosix,
1118
1131
  filePath: abs,
@@ -1120,9 +1133,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1120
1133
  readFile: options.readFile,
1121
1134
  readFileDigest: options.readFileDigest,
1122
1135
  signal: options.signal,
1123
- });
1124
- if (record) files.push(record);
1125
- else assertSnapshotVanishIsSafe(relPosix, base);
1136
+ }));
1126
1137
  }
1127
1138
  }
1128
1139
 
@@ -1208,7 +1219,7 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
1208
1219
  })) {
1209
1220
  throw new Error(`offline sync snapshot path is excluded: ${relPath}`);
1210
1221
  }
1211
- const record = await readOfflineSyncFileRecord({
1222
+ files.push(await readOfflineSyncFileRecord({
1212
1223
  root,
1213
1224
  relPath,
1214
1225
  filePath,
@@ -1216,9 +1227,7 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
1216
1227
  readFile: options.readFile,
1217
1228
  readFileDigest: options.readFileDigest,
1218
1229
  signal: options.signal,
1219
- });
1220
- if (record) files.push(record);
1221
- else assertSnapshotVanishIsSafe(relPath, undefined);
1230
+ }));
1222
1231
  }
1223
1232
  throwIfOfflineSyncAborted(options.signal);
1224
1233
 
@@ -1414,6 +1423,11 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
1414
1423
  excludeNodeLocalState?: boolean;
1415
1424
  readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
1416
1425
  }): Promise<OfflineSyncChangeset> {
1426
+ const root = await prepareSafeArchiveRoot(
1427
+ path.resolve(options.root),
1428
+ "buildOfflineSyncChangesetFromSnapshot",
1429
+ "root",
1430
+ );
1417
1431
  const includeTranscripts = options.includeTranscripts !== false;
1418
1432
  const excludedPaths = new Set(
1419
1433
  (options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]")),
@@ -1453,10 +1467,10 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
1453
1467
  now: options.now,
1454
1468
  userExcludeRegexps: options.userExcludeRegexps,
1455
1469
  readFile: options.readFile,
1456
- });
1470
+ }).catch(censusRaceAsChangedWhileBuilding(relPath));
1457
1471
  const record = file.files[0];
1458
1472
  if (!record || typeof record.contentBase64 !== "string" || record.sha256 !== currentEntry.sha256) {
1459
- throw new Error(`offline sync file changed while building changeset: ${relPath}`);
1473
+ throw changedWhileBuildingChangesetError(relPath);
1460
1474
  }
1461
1475
  changes.push({
1462
1476
  type: "upsert",
@@ -1467,6 +1481,8 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
1467
1481
  continue;
1468
1482
  }
1469
1483
  if (!currentEntry && baseEntry) {
1484
+ // Census miss is not confirmed delete — recheck disk (transient ENOENT / recreate).
1485
+ if (await offlineSyncRegularFilePresent(root, relPath)) continue;
1470
1486
  const mtimeMs = deletionMtimeByPath?.get(relPath);
1471
1487
  changes.push({
1472
1488
  type: "delete",
@@ -1808,6 +1824,12 @@ export async function applyOfflineSyncSnapshot(options: {
1808
1824
  continue;
1809
1825
  }
1810
1826
  if (!currentEntry && !base) {
1827
+ // Census miss with on-disk file: uncertain local presence, do not overwrite.
1828
+ if (await offlineSyncRegularFilePresent(root, relPath)) {
1829
+ pendingLocal += 1;
1830
+ skipped += 1;
1831
+ continue;
1832
+ }
1811
1833
  await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
1812
1834
  nextBase.set(relPath, toFileState(incoming));
1813
1835
  upserted += 1;
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import test from "node:test";
@@ -1547,3 +1548,50 @@ test("startupSearchSync composes its caller abort into the QMD probe", async ()
1547
1548
  await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
1548
1549
  }
1549
1550
  });
1551
+
1552
+ // Both startup call sites must open read readiness without migration replay.
1553
+ for (const namespace of ["default", "shared"]) {
1554
+ test(`startup defers entity migration for ${namespace} storage`, async () => {
1555
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-init-migration-defer-"));
1556
+ const orchestrator = new Orchestrator(
1557
+ parseConfig({
1558
+ memoryDir,
1559
+ workspaceDir: path.join(memoryDir, "workspace"),
1560
+ qmdEnabled: false,
1561
+ namespacesEnabled: namespace === "shared",
1562
+ namespaceCatalogEnabled: false,
1563
+ defaultNamespace: "default",
1564
+ sharedNamespace: "shared",
1565
+ namespacePolicies: [],
1566
+ embeddingFallbackEnabled: false,
1567
+ identityContinuityEnabled: false,
1568
+ transcriptEnabled: false,
1569
+ hourlySummariesEnabled: false,
1570
+ compoundingEnabled: false,
1571
+ knowledgeIndexEnabled: false,
1572
+ }),
1573
+ );
1574
+ const storage = await orchestrator.getStorage(namespace);
1575
+ const lockPath = path.join(storage.dir, "state", "entity-canonical-id-migration.lock");
1576
+ const journalPath = path.join(storage.dir, "state", "entity-canonical-id-migration-v1.json");
1577
+ try {
1578
+ await mkdir(path.dirname(lockPath), { recursive: true });
1579
+ await writeFile(lockPath, `${process.pid} init-migration-test ${new Date().toISOString()}\n`);
1580
+ const outcome = await withTestDeadline(
1581
+ orchestrator.initialize().then(() => "settled" as const, () => "rejected" as const),
1582
+ );
1583
+ assert.equal(outcome, "settled", "startup must succeed while the migration lock is held");
1584
+ assert.equal(existsSync(lockPath), true);
1585
+ assert.equal(existsSync(journalPath), false, "startup must not run the migration");
1586
+ await rm(lockPath);
1587
+ await storage.ensureDirectories();
1588
+ // SAFETY: the journal is written by the migration under test and carries an optional boolean `complete`.
1589
+ const state = JSON.parse(await readFile(journalPath, "utf8")) as { complete?: boolean };
1590
+ assert.equal(state.complete, true, "default setup must still run migration");
1591
+ } finally {
1592
+ await rm(lockPath, { force: true });
1593
+ await orchestrator.destroy();
1594
+ await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
1595
+ }
1596
+ });
1597
+ }
@@ -746,6 +746,7 @@ export class Orchestrator {
746
746
  private _dependencyPropagationDelivery: DependencyPropagationDelivery | undefined;
747
747
  private _dependencyPropagationRecovery: Promise<void> | null = null;
748
748
  private destroyed = false;
749
+ private acceptingEntityMigrationMaintenance = true;
749
750
 
750
751
  private get dependencyPropagationDelivery(): DependencyPropagationDelivery {
751
752
  if (this.destroyed) throw new Error("orchestrator has been destroyed");
@@ -967,6 +968,7 @@ export class Orchestrator {
967
968
  * commands should call it before returning to let Node exit naturally.
968
969
  */
969
970
  async destroy(): Promise<void> {
971
+ this.acceptingEntityMigrationMaintenance = false;
970
972
  this._orchestratorInitCoordinator?.retireStartupEpoch();
971
973
  this.abortDeferredInit();
972
974
  this.extractionQueueCoordinator.stopAccepting();
@@ -1499,14 +1501,6 @@ export class Orchestrator {
1499
1501
  (namespace) => this.namespaceSearchRouter.backendForNamespace(namespace),
1500
1502
  (msg) => log.debug(msg),
1501
1503
  );
1502
- const defaultNs = this.config.defaultNamespace;
1503
- this.storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, defaultNs);
1504
- this.storageRouter.onStorageCreated = (storage, namespace) => {
1505
- storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
1506
- };
1507
- this.storageRouter.forEachCachedStorage((storage, namespace) => {
1508
- storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
1509
- });
1510
1504
  }
1511
1505
  this.maintenanceScheduler = new MaintenanceScheduler({
1512
1506
  config,
@@ -1516,6 +1510,13 @@ export class Orchestrator {
1516
1510
  getStorage: () => this.storage, storageForNamespace: (namespace) => this.storageRouter.storageFor(namespace),
1517
1511
  onActivitySynced: (summary) => { if (!this.config.meetings.enabled) return; void this.getMeetingsService().then((s) => s.requestBuildForActivitySync(summary.ranAt, this.config.activity.timezone, summary.touchedDates)).catch((err) => log.warn(`meetings: activity tail-step build failed to schedule: ${err instanceof Error ? err.message : String(err)}`)); },
1518
1512
  });
1513
+ const bindStorage = (storage: StorageManager, namespace: string) => {
1514
+ storage.onEntityMigrationComplete = () => { if (this.acceptingEntityMigrationMaintenance) this.requestQmdMaintenance(); };
1515
+ if (this.prioritizedEmbedding) storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
1516
+ };
1517
+ bindStorage(this.storage, this.config.defaultNamespace);
1518
+ this.storageRouter.onStorageCreated = bindStorage;
1519
+ this.storageRouter.forEachCachedStorage(bindStorage);
1519
1520
  // Issue #1526: background extraction queue lives on its own coordinator.
1520
1521
  this.extractionQueueCoordinator = new ExtractionQueueCoordinator();
1521
1522
 
@@ -1,3 +1,4 @@
1
+ import { log } from "../logger.js";
1
2
  import type { EntityFile, MemoryFile } from "../types.js";
2
3
  import {
3
4
  getFingerprint,
@@ -30,6 +31,7 @@ export interface EntityCanonicalIdMigrationHost {
30
31
  invalidateColdMemoriesCache(): void;
31
32
  bumpMemoryStatusVersion(): void;
32
33
  getEntityMutationVersion(): number;
34
+ onEntityMigrationComplete?: () => void;
33
35
  }
34
36
 
35
37
  export async function runLegacyEntityCanonicalIdMigration(
@@ -40,6 +42,9 @@ export async function runLegacyEntityCanonicalIdMigration(
40
42
  ): Promise<string | undefined> {
41
43
  const readMigrationFingerprint = () =>
42
44
  getFingerprint(host.baseDir, host.entitiesDir, () => String(host.getEntityMutationVersion()));
45
+ // The migration bumps the status version only after a pass that rewrote
46
+ // references; a no-op replay must not ask search maintenance to run.
47
+ let rewroteReferences = false;
43
48
  const completionFingerprint = await migrateLegacyEntityCanonicalIds({
44
49
  stateDir: host.stateDir,
45
50
  entitiesDir: host.entitiesDir,
@@ -65,9 +70,19 @@ export async function runLegacyEntityCanonicalIdMigration(
65
70
  invalidateKnowledgeIndexCache: host.invalidateKnowledgeIndexCache.bind(host),
66
71
  invalidateAllMemoriesCache: host.invalidateAllMemoriesCache.bind(host),
67
72
  invalidateColdMemoriesCache: host.invalidateColdMemoriesCache.bind(host),
68
- bumpMemoryStatusVersion: host.bumpMemoryStatusVersion.bind(host),
73
+ bumpMemoryStatusVersion: () => {
74
+ rewroteReferences = true;
75
+ host.bumpMemoryStatusVersion();
76
+ },
69
77
  readMigrationFingerprint,
70
78
  } satisfies EntityCanonicalIdMigrationDependencies);
79
+ if (completionFingerprint !== undefined && rewroteReferences) {
80
+ try {
81
+ host.onEntityMigrationComplete?.();
82
+ } catch {
83
+ log.warn("entity migration: search maintenance scheduling failed");
84
+ }
85
+ }
71
86
  // An undefined completion fingerprint is a deliberate signal (issue #2213):
72
87
  // a reconcile marker landed while the run scanned, and recomputing the
73
88
  // fingerprint here would seal that pending request into the runner's
@@ -46,7 +46,8 @@ const ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;
46
46
  const EntityCanonicalIdMigrationStateSchema = z.object({
47
47
  version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
48
48
  complete: z.boolean(),
49
- mappings: z.record(z.string().min(1)),
49
+ mappings: z.record(z.string(), z.string().min(1)),
50
+ completionFingerprint: z.string().optional(),
50
51
  /**
51
52
  * Pairs the migration refused to resolve, PARKED rather than dropped.
52
53
  * Deleting the record would strand the collision: once an operator removes
@@ -54,7 +55,7 @@ const EntityCanonicalIdMigrationStateSchema = z.object({
54
55
  * still naming the legacy id would never be rewritten. Optional so a journal
55
56
  * written by an older build still parses.
56
57
  */
57
- blocked: z.record(z.string().min(1)).optional(),
58
+ blocked: z.record(z.string(), z.string().min(1)).optional(),
58
59
  });
59
60
 
60
61
  type EntityCanonicalIdMigrationState = z.infer<typeof EntityCanonicalIdMigrationStateSchema>;
@@ -370,6 +371,13 @@ async function assertEntityContentUnchanged(
370
371
  }
371
372
  }
372
373
 
374
+ /**
375
+ * Fail-closed entity-entry validation for the startup path: refuses a
376
+ * symlinked or non-directory entities root and any symlinked entry, without
377
+ * taking the migration lock. Exported so `ensureDirectories()` can run it
378
+ * before deferring the migration (#3139) — the same check
379
+ * `migrateLegacyEntityCanonicalIds` performs under the lock.
380
+ */
373
381
  export async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
374
382
  const rootStat = await lstat(entitiesDir);
375
383
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
@@ -887,14 +895,11 @@ export async function migrateLegacyEntityCanonicalIds(
887
895
  if (!isErrnoCode(error, "ENOENT")) throw error;
888
896
  }
889
897
  }
890
- let state = await readState(deps);
891
- if (!state) {
892
- state = {
893
- version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,
894
- complete: false,
895
- mappings: {},
896
- };
897
- }
898
+ let state: EntityCanonicalIdMigrationState = await readState(deps) ?? {
899
+ version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,
900
+ complete: false,
901
+ mappings: {},
902
+ };
898
903
  const blocked: BlockedEntityPairs = new Map();
899
904
  const scanNotes: EntityScanNotes = { junk: new Set(), malformed: new Set() };
900
905
  // Parks for pairs whose files are GONE, tracked separately from
@@ -951,6 +956,10 @@ export async function migrateLegacyEntityCanonicalIds(
951
956
  // fingerprint could SEAL a marker the check never saw.
952
957
  const completionFingerprint = await deps.readMigrationFingerprint?.();
953
958
  if (await fileExists(reconcileMarkerPath)) return undefined;
959
+ if (completionFingerprint !== undefined && state.completionFingerprint !== completionFingerprint) {
960
+ state = { ...state, complete: true, completionFingerprint };
961
+ await publishState(deps, state);
962
+ }
954
963
  return completionFingerprint;
955
964
  };
956
965
  /**
@@ -16,6 +16,7 @@ import { statSync } from "node:fs";
16
16
  * reconciliation pass instead ({@link requestEntityCanonicalIdReconcile}).
17
17
  */
18
18
  import path from "node:path";
19
+ import { z } from "zod";
19
20
  import {
20
21
  closeSync,
21
22
  constants,
@@ -112,6 +113,29 @@ function readJournalFileNoFollow(statePath: string): JournalRead {
112
113
  }
113
114
  }
114
115
 
116
+ /** The journal fields completion attestation needs; extra journal fields are ignored. */
117
+ const CompletedJournalAttestationSchema = z.object({
118
+ version: z.literal(1),
119
+ complete: z.literal(true),
120
+ completionFingerprint: z.string(),
121
+ });
122
+
123
+ /** Skip compatibility scans only when journal completion attests to the live inputs. */
124
+ export async function entityCanonicalIdMigrationCompleted(
125
+ stateDir: string,
126
+ readFingerprint: () => Promise<string>,
127
+ ): Promise<boolean> {
128
+ const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
129
+ if (result.kind !== "ok") return false;
130
+ try {
131
+ const parsed = CompletedJournalAttestationSchema.safeParse(JSON.parse(result.raw));
132
+ if (!parsed.success) return false;
133
+ return parsed.data.completionFingerprint === await readFingerprint();
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
115
139
  export function loadHistoricalEntityCanonicalIds(stateDir: string): Readonly<Record<string, string>> {
116
140
  const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
117
141
  if (result.kind !== "ok") return {};
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Regression: amortized completion must refuse a symlinked entities root
3
+ * before getFingerprint → fingerprintEntityEntries readdir.
4
+ *
5
+ * inputSig already labels the root "L", but isCompleted still runs the full
6
+ * fingerprint (external tree walk) before any isTrustedEntitiesRoot-style
7
+ * guard. A planted complete journal makes that path reachable after a
8
+ * missing-id ENOENT.
9
+ *
10
+ * Instrumentation (deterministic): spawn a plain child (not nested --test —
11
+ * node skips recursive test runs) with --experimental-test-module-mocks that
12
+ * wraps node:fs/promises.readdir via mock.module, dynamic-imports the real
13
+ * EntityMigrationCompletionCache, and records every readdir whose realpath is
14
+ * the external canary (or whose path is the entities symlink). Assert
15
+ * externalCount === 0 — not merely completed === false. chmod/EACCES is not
16
+ * used (false-green on this host).
17
+ *
18
+ * Dynamic import in the probe is intentional: mock.module must install before
19
+ * the cache module loads fs.promises (module-loading boundary).
20
+ *
21
+ * Temp roots only. Expected today: RED (externalCount >= 1).
22
+ * After root nofollow guard: externalCount === 0 and completed === false.
23
+ */
24
+ import assert from "node:assert/strict";
25
+ import { spawnSync } from "node:child_process";
26
+ import { existsSync } from "node:fs";
27
+ import {
28
+ lstat,
29
+ mkdir,
30
+ mkdtemp,
31
+ readFile,
32
+ realpath,
33
+ rm,
34
+ symlink,
35
+ writeFile,
36
+ } from "node:fs/promises";
37
+ import os from "node:os";
38
+ import path from "node:path";
39
+ import test from "node:test";
40
+ import { fileURLToPath } from "node:url";
41
+
42
+ import { ENTITY_CANONICAL_ID_MIGRATION_FILE } from "./entity-canonical-id-references.js";
43
+
44
+ function findRepoRoot(startDir: string): string {
45
+ let dir = startDir;
46
+ for (;;) {
47
+ if (existsSync(path.join(dir, "packages", "remnic-core", "package.json"))) return dir;
48
+ const parent = path.dirname(dir);
49
+ if (parent === dir) {
50
+ throw new Error(`remnic repo root not found from ${startDir}`);
51
+ }
52
+ dir = parent;
53
+ }
54
+ }
55
+
56
+ const REPO_ROOT = findRepoRoot(path.dirname(fileURLToPath(import.meta.url)));
57
+ const CACHE_MODULE = path.join(
58
+ REPO_ROOT,
59
+ "packages",
60
+ "remnic-core",
61
+ "src",
62
+ "storage",
63
+ "entity-migration-completion-cache.ts",
64
+ );
65
+
66
+ type ProbeResult = {
67
+ completed: boolean | null;
68
+ err: string | null;
69
+ externalCount: number;
70
+ seen: Array<{ raw: string; real: string }>;
71
+ canaryReal: string;
72
+ };
73
+
74
+ test("isCompleted must not readdir through a symlinked entities root", async () => {
75
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-entities-root-nofollow-"));
76
+ const memoryDir = path.join(root, "memory");
77
+ const canaryDir = path.join(root, "canary-external");
78
+ const entitiesDir = path.join(memoryDir, "entities");
79
+ const stateDir = path.join(memoryDir, "state");
80
+ const probeFile = path.join(root, "completion-cache-nofollow.probe.mts");
81
+ const resultFile = path.join(root, "probe-result.json");
82
+
83
+ try {
84
+ await mkdir(stateDir, { recursive: true });
85
+ await mkdir(path.join(memoryDir, "config"), { recursive: true });
86
+ await mkdir(canaryDir, { recursive: true });
87
+ await writeFile(path.join(canaryDir, "outside-only.md"), "canary external entity body\n");
88
+
89
+ await symlink(canaryDir, entitiesDir);
90
+ assert.equal(
91
+ (await lstat(entitiesDir)).isSymbolicLink(),
92
+ true,
93
+ "fixture: entities/ must be a symlink to the external canary directory",
94
+ );
95
+ const canaryReal = await realpath(canaryDir);
96
+
97
+ // Regular complete journal — completionFingerprint compare invokes getFingerprint.
98
+ await writeFile(
99
+ path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE),
100
+ JSON.stringify({
101
+ version: 1,
102
+ complete: true,
103
+ mappings: {},
104
+ completionFingerprint: "planted-complete-journal-for-symlink-root-regression",
105
+ }),
106
+ );
107
+
108
+ // Plain probe script (no nested --test: parent already runs under the test
109
+ // runner, and node skips recursive test-file execution).
110
+ // Dynamic import loads cache after mock.module wraps readdir.
111
+ await writeFile(
112
+ probeFile,
113
+ `import { mock } from "node:test";
114
+ import path from "node:path";
115
+ import { writeFile } from "node:fs/promises";
116
+ import { readdir as realReaddir, realpath } from "node:fs/promises";
117
+
118
+ const canaryReal = ${JSON.stringify(canaryReal)};
119
+ const memoryDir = ${JSON.stringify(memoryDir)};
120
+ const entitiesDir = ${JSON.stringify(entitiesDir)};
121
+ const cacheModule = ${JSON.stringify(CACHE_MODULE)};
122
+ const resultFile = ${JSON.stringify(resultFile)};
123
+ const seen = [];
124
+
125
+ const fsp = await import("node:fs/promises");
126
+ mock.module("node:fs/promises", {
127
+ namedExports: {
128
+ ...fsp,
129
+ readdir: async (p, ...a) => {
130
+ const raw = path.resolve(String(p));
131
+ let real = raw;
132
+ try { real = await realpath(raw); } catch { /* keep raw */ }
133
+ seen.push({ raw, real });
134
+ return realReaddir(p, ...a);
135
+ },
136
+ },
137
+ });
138
+
139
+ // Dynamic import: mock.module must wrap fs.promises before the cache loads it.
140
+ const { EntityMigrationCompletionCache } = await import(cacheModule);
141
+ const cache = new EntityMigrationCompletionCache();
142
+ let completed = null;
143
+ let err = null;
144
+ try {
145
+ completed = await cache.isCompleted({
146
+ baseDir: memoryDir,
147
+ entitiesDir,
148
+ getEntityMutationVersion: () => 0,
149
+ });
150
+ } catch (e) {
151
+ err = e && typeof e === "object" && "code" in e ? String(e.code) : String(e);
152
+ }
153
+ const entitiesResolved = path.resolve(entitiesDir);
154
+ const external = seen.filter((s) => s.real === canaryReal || s.raw === entitiesResolved);
155
+ const payload = {
156
+ completed,
157
+ err,
158
+ seen,
159
+ externalCount: external.length,
160
+ canaryReal,
161
+ };
162
+ await writeFile(resultFile, JSON.stringify(payload), "utf8");
163
+ console.log(JSON.stringify(payload));
164
+ mock.reset();
165
+ `,
166
+ );
167
+
168
+ const probe = spawnSync(
169
+ process.execPath,
170
+ [
171
+ "--experimental-test-module-mocks",
172
+ "--import",
173
+ "tsx",
174
+ "--conditions=remnic-source",
175
+ probeFile,
176
+ ],
177
+ {
178
+ encoding: "utf8",
179
+ cwd: REPO_ROOT,
180
+ env: {
181
+ ...process.env,
182
+ NODE_PATH: path.join(REPO_ROOT, "node_modules"),
183
+ },
184
+ timeout: 60_000,
185
+ },
186
+ );
187
+
188
+ if (probe.error) {
189
+ assert.fail(`probe failed to start: ${probe.error.message}`);
190
+ }
191
+
192
+ let result: ProbeResult;
193
+ try {
194
+ // SAFETY: the child probe in this file writes `resultFile` as a ProbeResult before exiting.
195
+ result = JSON.parse(await readFile(resultFile, "utf8")) as ProbeResult;
196
+ } catch {
197
+ assert.fail(
198
+ [
199
+ "probe did not write result file",
200
+ `status=${String(probe.status)} signal=${String(probe.signal)}`,
201
+ `stdout:\n${probe.stdout ?? ""}`,
202
+ `stderr:\n${probe.stderr ?? ""}`,
203
+ ].join("\n"),
204
+ );
205
+ return;
206
+ }
207
+
208
+ assert.equal(
209
+ result.externalCount,
210
+ 0,
211
+ [
212
+ "isCompleted must not readdir a symlinked entities root (external canary).",
213
+ `externalCount=${result.externalCount} canaryReal=${result.canaryReal}`,
214
+ `seen=${JSON.stringify(result.seen)}`,
215
+ result.err ? `isCompleted err=${result.err}` : `completed=${String(result.completed)}`,
216
+ ].join(" "),
217
+ );
218
+ assert.equal(result.err, null, "untrusted root must fail closed without throwing through external IO");
219
+ assert.equal(result.completed, false, "untrusted entities root must not report migration complete");
220
+ } finally {
221
+ await rm(root, { recursive: true, force: true });
222
+ }
223
+ });