@remnic/core 10.0.48 → 10.0.50

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 (449) hide show
  1. package/dist/access-admin-ops-surface.js +73 -70
  2. package/dist/access-audit.js +2 -2
  3. package/dist/access-authorization-probe.js +74 -71
  4. package/dist/access-boundary.js +73 -70
  5. package/dist/access-cli.js +170 -167
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +73 -70
  8. package/dist/access-http-briefing.d.ts +2 -0
  9. package/dist/access-http-briefing.d.ts.map +1 -1
  10. package/dist/access-http-briefing.js +74 -71
  11. package/dist/access-http-query.js +75 -72
  12. package/dist/access-http.d.ts.map +1 -1
  13. package/dist/access-http.js +96 -93
  14. package/dist/access-identity-continuity-surface.js +73 -70
  15. package/dist/access-lcm-surface.js +73 -70
  16. package/dist/access-mcp.js +85 -82
  17. package/dist/access-namespace-preflight.js +73 -70
  18. package/dist/access-observe-write-category.d.ts +1 -1
  19. package/dist/access-observe-write-category.d.ts.map +1 -1
  20. package/dist/access-observe-write-category.js +15 -14
  21. package/dist/access-observe-write-surface.d.ts.map +1 -1
  22. package/dist/access-observe-write-surface.js +73 -70
  23. package/dist/access-offline-manifest.js +9 -8
  24. package/dist/access-operations-batch.js +76 -73
  25. package/dist/access-operations.js +83 -80
  26. package/dist/access-recall-concurrency.js +73 -70
  27. package/dist/access-recall-response.js +73 -70
  28. package/dist/access-recall-surface.js +73 -70
  29. package/dist/access-schema.js +11 -10
  30. package/dist/access-service.d.ts +5 -0
  31. package/dist/access-service.d.ts.map +1 -1
  32. package/dist/access-service.js +73 -70
  33. package/dist/access-surface-catalog.js +75 -72
  34. package/dist/access-surface-catalog.js.map +1 -1
  35. package/dist/active-recall.js +32 -30
  36. package/dist/active-recall.js.map +1 -1
  37. package/dist/adapters/index.js +4 -4
  38. package/dist/adapters/registry.js +2 -2
  39. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  40. package/dist/briefing-deadline.d.ts +7 -0
  41. package/dist/briefing-deadline.d.ts.map +1 -0
  42. package/dist/briefing-deadline.js +13 -0
  43. package/dist/briefing-window.d.ts +12 -4
  44. package/dist/briefing-window.d.ts.map +1 -1
  45. package/dist/briefing-window.js +3 -1
  46. package/dist/briefing.d.ts +2 -0
  47. package/dist/briefing.d.ts.map +1 -1
  48. package/dist/briefing.js +22 -20
  49. package/dist/buffer.js +6 -6
  50. package/dist/calibration.js +2 -2
  51. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  52. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  53. package/dist/capture-write-lock-scope.d.ts +7 -0
  54. package/dist/capture-write-lock-scope.d.ts.map +1 -0
  55. package/dist/capture-write-lock-scope.js +10 -0
  56. package/dist/causal-behavior.js +2 -2
  57. package/dist/causal-chain.js +2 -2
  58. package/dist/causal-consolidation.js +23 -22
  59. package/dist/causal-consolidation.js.map +1 -1
  60. package/dist/causal-retrieval.js +2 -2
  61. package/dist/causal-trajectory-graph.js +3 -3
  62. package/dist/causal-trajectory.js +1 -1
  63. package/dist/{chunk-FAKRTS3Y.js → chunk-2AKIVSHP.js} +2 -2
  64. package/dist/{chunk-EIYJK7OD.js → chunk-2M36PR7R.js} +2 -2
  65. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  66. package/dist/{chunk-6P6MZDBL.js → chunk-3NL4ALUP.js} +2 -2
  67. package/dist/{chunk-6CC2YCKG.js → chunk-43TUWQNR.js} +3 -3
  68. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  69. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  70. package/dist/{chunk-5KGYR5VN.js → chunk-5DVOBBTV.js} +2 -2
  71. package/dist/{chunk-RT2GN6KQ.js → chunk-5FUDSK6Z.js} +2 -2
  72. package/dist/{chunk-B4XSXFKI.js → chunk-5G53JSRO.js} +2 -2
  73. package/dist/{chunk-BGK3SFCC.js → chunk-5X2H3X4J.js} +2 -2
  74. package/dist/{chunk-OXSTKCYK.js → chunk-6AEKSOWV.js} +33 -5
  75. package/dist/chunk-6AEKSOWV.js.map +1 -0
  76. package/dist/{chunk-4J7BNL7B.js → chunk-6ZQX3R66.js} +2 -2
  77. package/dist/{chunk-JNLYHJG7.js → chunk-74AY3S7U.js} +4 -4
  78. package/dist/{chunk-RUAIO5UA.js → chunk-7J6A5MV2.js} +2 -2
  79. package/dist/{chunk-A45QXZMU.js → chunk-7SAE6T36.js} +2 -2
  80. package/dist/chunk-7VLBZKNB.js +26 -0
  81. package/dist/chunk-7VLBZKNB.js.map +1 -0
  82. package/dist/{chunk-QE2XQM3S.js → chunk-7YVER7ZO.js} +2 -2
  83. package/dist/{chunk-IJH6METJ.js → chunk-A3UFV4K3.js} +4 -4
  84. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  85. package/dist/chunk-A5L5KGDI.js.map +1 -0
  86. package/dist/{chunk-LXN737FX.js → chunk-AAANTXCQ.js} +2 -2
  87. package/dist/{chunk-I3IPXGFQ.js → chunk-AJJPXUE6.js} +4 -4
  88. package/dist/{chunk-2DPJFJFH.js → chunk-BNJAVRNJ.js} +11 -11
  89. package/dist/{chunk-YL564QS2.js → chunk-CCL2OXFO.js} +3 -3
  90. package/dist/{chunk-CHSRA76M.js → chunk-CRN3EPE2.js} +4 -3
  91. package/dist/chunk-CRN3EPE2.js.map +1 -0
  92. package/dist/{chunk-ZX5N5D6M.js → chunk-DAOMWGO2.js} +4 -4
  93. package/dist/{chunk-YUE7J2HZ.js → chunk-DMKTOCC7.js} +2 -2
  94. package/dist/chunk-DMKTOCC7.js.map +1 -0
  95. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  96. package/dist/chunk-EKUI7I2X.js.map +1 -0
  97. package/dist/{chunk-PKPOO6X5.js → chunk-EKYWZYZJ.js} +25 -17
  98. package/dist/chunk-EKYWZYZJ.js.map +1 -0
  99. package/dist/{chunk-23ZMQONL.js → chunk-ERMT5WQM.js} +2 -2
  100. package/dist/{chunk-VTCNVGM6.js → chunk-EWBXMGXR.js} +2 -2
  101. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  102. package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
  103. package/dist/{chunk-2JNBPOZ2.js → chunk-GDYLZ6FR.js} +3 -3
  104. package/dist/{chunk-DZPTR2XQ.js → chunk-GFAEKSFR.js} +2 -2
  105. package/dist/{chunk-L46MG2GE.js → chunk-GQIYL7OD.js} +400 -301
  106. package/dist/chunk-GQIYL7OD.js.map +1 -0
  107. package/dist/{chunk-M7LA4QBY.js → chunk-GXVXBYRA.js} +3 -3
  108. package/dist/{chunk-UXAF4RQM.js → chunk-HKOMHP6D.js} +27 -15
  109. package/dist/chunk-HKOMHP6D.js.map +1 -0
  110. package/dist/{chunk-JGMJZBFV.js → chunk-HPNXS6W6.js} +2 -2
  111. package/dist/{chunk-BFPF5N3H.js → chunk-HXFIILGF.js} +4 -4
  112. package/dist/{chunk-YBKQAVPZ.js → chunk-IMKOJVD4.js} +101 -81
  113. package/dist/chunk-IMKOJVD4.js.map +1 -0
  114. package/dist/{chunk-CMNL3QSL.js → chunk-IN6U2BS5.js} +2 -2
  115. package/dist/{chunk-BWRX322G.js → chunk-IUYXCTA7.js} +2 -2
  116. package/dist/{chunk-LU57EEJW.js → chunk-JBWD3J5U.js} +3 -3
  117. package/dist/chunk-JNA7LFMQ.js +41 -0
  118. package/dist/chunk-JNA7LFMQ.js.map +1 -0
  119. package/dist/{chunk-2V664H6O.js → chunk-JS44PBCM.js} +5 -5
  120. package/dist/{chunk-XKTHJHSQ.js → chunk-JZCBMKR5.js} +3 -3
  121. package/dist/{chunk-MXPVNDGO.js → chunk-K5QQXVG6.js} +12 -12
  122. package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
  123. package/dist/{chunk-WUBGMV3G.js → chunk-KRAMCBIW.js} +9 -9
  124. package/dist/{chunk-NDMPRT3Z.js → chunk-KW5YZELC.js} +2 -2
  125. package/dist/{chunk-2RQR4TXV.js → chunk-LIBVANOF.js} +2 -2
  126. package/dist/{chunk-M7L5A2AT.js → chunk-LMMBV7QC.js} +4 -4
  127. package/dist/{chunk-PJBYVJKO.js → chunk-LXXIV7Y2.js} +2 -2
  128. package/dist/{chunk-MZCTS3ZG.js → chunk-M2DOJBH7.js} +2 -2
  129. package/dist/{chunk-WOPZ2P5Z.js → chunk-M4HRYLOA.js} +2 -2
  130. package/dist/{chunk-JCYEDMOY.js → chunk-M6TP6UYD.js} +3 -3
  131. package/dist/{chunk-KAUCVTAW.js → chunk-MLNXATFG.js} +2 -2
  132. package/dist/{chunk-2NX7JTST.js → chunk-MPWOAIIH.js} +2 -2
  133. package/dist/{chunk-RQLTCHUL.js → chunk-MWURUSRE.js} +13 -13
  134. package/dist/{chunk-3EGL7XZO.js → chunk-N74WJ7B2.js} +2 -2
  135. package/dist/{chunk-DRXTMDAG.js → chunk-N7FNCE7F.js} +2 -2
  136. package/dist/{chunk-6CWAY4R7.js → chunk-NPLM7AFU.js} +2 -2
  137. package/dist/{chunk-VECRSYQG.js → chunk-NRGQ5HJO.js} +1 -1
  138. package/dist/{chunk-PPDNG7WC.js → chunk-NSP5PIQH.js} +2 -2
  139. package/dist/{chunk-6HVLHIAB.js → chunk-P7OVFGMU.js} +2 -2
  140. package/dist/{chunk-EX27VKU6.js → chunk-PDTNQLPC.js} +2 -2
  141. package/dist/{chunk-ME5LVISI.js → chunk-PJCVI44H.js} +2 -2
  142. package/dist/{chunk-LJCHDFLP.js → chunk-Q3PNQYAC.js} +2 -2
  143. package/dist/{chunk-YFJHFEET.js → chunk-QS765SBL.js} +2 -2
  144. package/dist/{chunk-FJ264QBP.js → chunk-QTDZ2NYC.js} +11 -11
  145. package/dist/{chunk-Q6UZREJK.js → chunk-RKRX4C4H.js} +2 -2
  146. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  147. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  148. package/dist/{chunk-GLZWOIU3.js → chunk-SJTSWG2Z.js} +3 -3
  149. package/dist/{chunk-Y57GJDG5.js → chunk-SWRRZNHM.js} +14 -17
  150. package/dist/chunk-SWRRZNHM.js.map +1 -0
  151. package/dist/{chunk-CCTLRRV6.js → chunk-TEH5MDX5.js} +55 -34
  152. package/dist/chunk-TEH5MDX5.js.map +1 -0
  153. package/dist/{chunk-XM747YJF.js → chunk-TFWJXWL6.js} +2 -2
  154. package/dist/{chunk-QFQ4UG3I.js → chunk-TXKQBQYW.js} +4 -4
  155. package/dist/{chunk-BMOJFGVX.js → chunk-UALXWWGN.js} +2 -2
  156. package/dist/{chunk-B44NFHWI.js → chunk-UNOAVHV4.js} +2 -2
  157. package/dist/{chunk-TF5NZGPS.js → chunk-UQBOPCDK.js} +2 -2
  158. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  159. package/dist/chunk-UWMZT2TY.js.map +1 -0
  160. package/dist/{chunk-5R2IF2KI.js → chunk-UXGXJUZ5.js} +7 -7
  161. package/dist/{chunk-FWV2SXSL.js → chunk-UZVQFGAY.js} +101 -98
  162. package/dist/chunk-UZVQFGAY.js.map +1 -0
  163. package/dist/{chunk-J6B6FK2X.js → chunk-VCOMACR6.js} +53 -53
  164. package/dist/{chunk-WCGJK4NQ.js → chunk-VKQI47DJ.js} +7 -7
  165. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  166. package/dist/{chunk-BCDECPVO.js → chunk-WV4UYDP6.js} +2 -2
  167. package/dist/{chunk-VWJ56T63.js → chunk-WXJC2L66.js} +2 -2
  168. package/dist/chunk-X2KO4FK5.js +40 -0
  169. package/dist/chunk-X2KO4FK5.js.map +1 -0
  170. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  171. package/dist/{chunk-FPANDKMQ.js → chunk-XC3H7KON.js} +2 -2
  172. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  173. package/dist/{chunk-3DKKZVXS.js → chunk-XTXWR6KQ.js} +26 -11
  174. package/dist/chunk-XTXWR6KQ.js.map +1 -0
  175. package/dist/{chunk-3SL52JKD.js → chunk-XVZA6VXC.js} +4 -4
  176. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  177. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  178. package/dist/{chunk-XTVH2NAW.js → chunk-YECEEJUU.js} +3 -3
  179. package/dist/{chunk-QR6W3FSW.js → chunk-Z5277JWF.js} +3 -3
  180. package/dist/{chunk-CTHYR4QK.js → chunk-ZFNXJEUG.js} +2 -2
  181. package/dist/{chunk-6Q56WDHG.js → chunk-ZLCBLDJO.js} +2 -2
  182. package/dist/{chunk-VY6BD5KB.js → chunk-ZMO2S5RT.js} +12 -10
  183. package/dist/chunk-ZMO2S5RT.js.map +1 -0
  184. package/dist/{chunk-KRACAE73.js → chunk-ZNRTHYYW.js} +6 -6
  185. package/dist/{chunk-7AIPWNM7.js → chunk-ZSMA67FV.js} +2 -2
  186. package/dist/cli.js +123 -120
  187. package/dist/coding/export-okf-codegraph.js +19 -18
  188. package/dist/coding/export-okf-codegraph.js.map +1 -1
  189. package/dist/coding/pre-action-gate.js +2 -2
  190. package/dist/compounding/engine.js +21 -20
  191. package/dist/config.js +32 -30
  192. package/dist/connectors/codex-materialize-runner.js +20 -19
  193. package/dist/connectors/index.js +20 -19
  194. package/dist/consolidation-provenance-check.js +4 -4
  195. package/dist/consolidation-undo.js +2 -2
  196. package/dist/convergence-refresh.js +22 -21
  197. package/dist/corpus-watermark.js +22 -21
  198. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  199. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  200. package/dist/dashboard-runtime.js +2 -2
  201. package/dist/deep-recall-http-glue.js +12 -11
  202. package/dist/deep-recall.js +2 -2
  203. package/dist/embedding-fallback.js +3 -2
  204. package/dist/embedding-generation-marker.d.ts +16 -0
  205. package/dist/embedding-generation-marker.d.ts.map +1 -0
  206. package/dist/embedding-generation-marker.js +19 -0
  207. package/dist/embedding-generation-marker.js.map +1 -0
  208. package/dist/embedding-index-storage.d.ts +2 -4
  209. package/dist/embedding-index-storage.d.ts.map +1 -1
  210. package/dist/embedding-index-storage.js +2 -1
  211. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-ZBNRGUNV.js} +3 -3
  212. package/dist/entity-canonical-id-migration-ZBNRGUNV.js.map +1 -0
  213. package/dist/entity-retrieval.js +22 -21
  214. package/dist/event-order-recall.js +2 -2
  215. package/dist/explicit-capture.js +3 -3
  216. package/dist/external-wiki-access.js +74 -71
  217. package/dist/extraction-span-materialize.js +2 -2
  218. package/dist/extraction.js +30 -29
  219. package/dist/{graph-edge-decay-3HVLMT6U.js → graph-edge-decay-IU75NV42.js} +6 -6
  220. package/dist/graph-edge-decay-IU75NV42.js.map +1 -0
  221. package/dist/graph-jsonl.js +4 -4
  222. package/dist/graph-snapshot.js +4 -4
  223. package/dist/graph.js +5 -5
  224. package/dist/harmonic-retrieval.js +20 -19
  225. package/dist/index.d.ts +2 -1
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +230 -218
  228. package/dist/lcm/engine.js +3 -3
  229. package/dist/lcm/index.js +5 -5
  230. package/dist/local-llm.js +2 -2
  231. package/dist/location/index.js +25 -24
  232. package/dist/location/tagging.d.ts +7 -5
  233. package/dist/location/tagging.d.ts.map +1 -1
  234. package/dist/maintenance/memory-governance.js +20 -19
  235. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -19
  236. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -21
  237. package/dist/maintenance/rebuild-memory-projection.js +21 -20
  238. package/dist/memory-browse-operations.js +74 -71
  239. package/dist/memory-browse.js +5 -5
  240. package/dist/namespaces/migrate.js +21 -20
  241. package/dist/namespaces/search.js +6 -6
  242. package/dist/namespaces/storage.js +20 -19
  243. package/dist/offline-sync-changeset-generations.js +5 -4
  244. package/dist/offline-sync-embedding-generation.d.ts +12 -2
  245. package/dist/offline-sync-embedding-generation.d.ts.map +1 -1
  246. package/dist/offline-sync-embedding-generation.js +6 -3
  247. package/dist/offline-sync-file-io.d.ts +15 -0
  248. package/dist/offline-sync-file-io.d.ts.map +1 -1
  249. package/dist/offline-sync-file-io.js +9 -1
  250. package/dist/offline-sync-snapshot-stream.js +9 -8
  251. package/dist/offline-sync-upload-staging.js +2 -2
  252. package/dist/offline-sync.d.ts.map +1 -1
  253. package/dist/offline-sync.js +10 -9
  254. package/dist/operator-doctor-corpus.js +23 -22
  255. package/dist/operator-toolkit.js +45 -43
  256. package/dist/orchestration/maintenance.js +33 -32
  257. package/dist/orchestrator.d.ts +1 -0
  258. package/dist/orchestrator.d.ts.map +1 -1
  259. package/dist/orchestrator.js +170 -167
  260. package/dist/page-versioning.js +2 -2
  261. package/dist/qmd.js +2 -2
  262. package/dist/recall-navigation-operations.js +74 -71
  263. package/dist/recall-navigation.js +73 -70
  264. package/dist/recall-state-view-admission.js +5 -5
  265. package/dist/recall-why-service.js +5 -5
  266. package/dist/resume-bundles.js +33 -31
  267. package/dist/search/factory.js +5 -5
  268. package/dist/search/index.js +9 -9
  269. package/dist/semantic-consolidation.js +25 -24
  270. package/dist/semantic-rule-promotion.js +20 -19
  271. package/dist/semantic-rule-verifier.js +20 -19
  272. package/dist/source-agent-qualifier.js +24 -23
  273. package/dist/standup/build.js +20 -19
  274. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  275. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  276. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  277. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  278. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  279. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  280. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  281. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  282. package/dist/storage/entity-store.d.ts +1 -8
  283. package/dist/storage/entity-store.d.ts.map +1 -1
  284. package/dist/storage/memory-read-store.d.ts +3 -2
  285. package/dist/storage/memory-read-store.d.ts.map +1 -1
  286. package/dist/storage/windowed-memory-read.d.ts +6 -0
  287. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  288. package/dist/storage.d.ts +2 -0
  289. package/dist/storage.d.ts.map +1 -1
  290. package/dist/storage.js +19 -18
  291. package/dist/summarizer.js +3 -3
  292. package/dist/support-passport/index.js +76 -73
  293. package/dist/transfer/backup.js +2 -2
  294. package/dist/transfer/capsule-export.js +2 -2
  295. package/dist/transfer/capsule-import.js +4 -4
  296. package/dist/transfer/export-okf.js +20 -19
  297. package/dist/verified-recall.js +20 -19
  298. package/dist/who-knows-cli.js +21 -20
  299. package/dist/who-knows.js +20 -19
  300. package/package.json +2 -2
  301. package/src/access-http-briefing.ts +4 -0
  302. package/src/access-http.test.ts +40 -11
  303. package/src/access-http.ts +6 -0
  304. package/src/access-observe-write-category.ts +1 -1
  305. package/src/access-observe-write-surface.ts +24 -19
  306. package/src/access-operations-batch.ts +18 -2
  307. package/src/access-service-peer-capture-backoff.test.ts +236 -0
  308. package/src/access-service.ts +11 -2
  309. package/src/briefing-abort.test.ts +173 -0
  310. package/src/briefing-deadline.test.ts +84 -0
  311. package/src/briefing-deadline.ts +33 -0
  312. package/src/briefing-window-abort.test.ts +47 -0
  313. package/src/briefing-window.ts +33 -4
  314. package/src/briefing.ts +23 -16
  315. package/src/capture-write-lock-scope.test.ts +32 -0
  316. package/src/capture-write-lock-scope.ts +30 -0
  317. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  318. package/src/correction/passive-correction-notifications.ts +4 -0
  319. package/src/embedding-generation-marker.ts +51 -0
  320. package/src/embedding-index-storage.ts +13 -29
  321. package/src/entity-lazy-compatibility.test.ts +229 -0
  322. package/src/entity-migration-completion-notification.test.ts +148 -0
  323. package/src/index.ts +2 -2
  324. package/src/location/tagging.ts +14 -12
  325. package/src/offline-sync-embedding-delete-digest.test.ts +58 -0
  326. package/src/offline-sync-embedding-generation-marker.test.ts +245 -0
  327. package/src/offline-sync-embedding-generation.ts +47 -2
  328. package/src/offline-sync-embedding-lock-loss.test.ts +42 -0
  329. package/src/offline-sync-file-io.ts +61 -2
  330. package/src/offline-sync.test.ts +313 -20
  331. package/src/offline-sync.ts +47 -22
  332. package/src/orchestrator-init.test.ts +49 -1
  333. package/src/orchestrator.ts +9 -8
  334. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  335. package/src/storage/entity-canonical-id-migration.ts +17 -8
  336. package/src/storage/entity-canonical-id-references.ts +24 -0
  337. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  338. package/src/storage/entity-migration-completion-cache.ts +153 -0
  339. package/src/storage/entity-store.ts +58 -86
  340. package/src/storage/memory-read-store.ts +9 -3
  341. package/src/storage/windowed-memory-read.ts +8 -0
  342. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  343. package/src/storage.ts +9 -3
  344. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  345. package/dist/chunk-3DKKZVXS.js.map +0 -1
  346. package/dist/chunk-CCTLRRV6.js.map +0 -1
  347. package/dist/chunk-CHSRA76M.js.map +0 -1
  348. package/dist/chunk-FRXS6SC7.js.map +0 -1
  349. package/dist/chunk-FWV2SXSL.js.map +0 -1
  350. package/dist/chunk-L46MG2GE.js.map +0 -1
  351. package/dist/chunk-LWRIGKFM.js.map +0 -1
  352. package/dist/chunk-OXSTKCYK.js.map +0 -1
  353. package/dist/chunk-PKPOO6X5.js.map +0 -1
  354. package/dist/chunk-SUKPM6DV.js.map +0 -1
  355. package/dist/chunk-UXAF4RQM.js.map +0 -1
  356. package/dist/chunk-VY6BD5KB.js.map +0 -1
  357. package/dist/chunk-Y57GJDG5.js.map +0 -1
  358. package/dist/chunk-YBKQAVPZ.js.map +0 -1
  359. package/dist/chunk-YUE7J2HZ.js.map +0 -1
  360. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  361. /package/dist/{capsule-crypto-GWVG7LGC.js.map → briefing-deadline.js.map} +0 -0
  362. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  363. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  364. /package/dist/{graph-edge-decay-3HVLMT6U.js.map → capture-write-lock-scope.js.map} +0 -0
  365. /package/dist/{chunk-FAKRTS3Y.js.map → chunk-2AKIVSHP.js.map} +0 -0
  366. /package/dist/{chunk-EIYJK7OD.js.map → chunk-2M36PR7R.js.map} +0 -0
  367. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  368. /package/dist/{chunk-6P6MZDBL.js.map → chunk-3NL4ALUP.js.map} +0 -0
  369. /package/dist/{chunk-6CC2YCKG.js.map → chunk-43TUWQNR.js.map} +0 -0
  370. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  371. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  372. /package/dist/{chunk-5KGYR5VN.js.map → chunk-5DVOBBTV.js.map} +0 -0
  373. /package/dist/{chunk-RT2GN6KQ.js.map → chunk-5FUDSK6Z.js.map} +0 -0
  374. /package/dist/{chunk-B4XSXFKI.js.map → chunk-5G53JSRO.js.map} +0 -0
  375. /package/dist/{chunk-BGK3SFCC.js.map → chunk-5X2H3X4J.js.map} +0 -0
  376. /package/dist/{chunk-4J7BNL7B.js.map → chunk-6ZQX3R66.js.map} +0 -0
  377. /package/dist/{chunk-JNLYHJG7.js.map → chunk-74AY3S7U.js.map} +0 -0
  378. /package/dist/{chunk-RUAIO5UA.js.map → chunk-7J6A5MV2.js.map} +0 -0
  379. /package/dist/{chunk-A45QXZMU.js.map → chunk-7SAE6T36.js.map} +0 -0
  380. /package/dist/{chunk-QE2XQM3S.js.map → chunk-7YVER7ZO.js.map} +0 -0
  381. /package/dist/{chunk-IJH6METJ.js.map → chunk-A3UFV4K3.js.map} +0 -0
  382. /package/dist/{chunk-LXN737FX.js.map → chunk-AAANTXCQ.js.map} +0 -0
  383. /package/dist/{chunk-I3IPXGFQ.js.map → chunk-AJJPXUE6.js.map} +0 -0
  384. /package/dist/{chunk-2DPJFJFH.js.map → chunk-BNJAVRNJ.js.map} +0 -0
  385. /package/dist/{chunk-YL564QS2.js.map → chunk-CCL2OXFO.js.map} +0 -0
  386. /package/dist/{chunk-ZX5N5D6M.js.map → chunk-DAOMWGO2.js.map} +0 -0
  387. /package/dist/{chunk-23ZMQONL.js.map → chunk-ERMT5WQM.js.map} +0 -0
  388. /package/dist/{chunk-VTCNVGM6.js.map → chunk-EWBXMGXR.js.map} +0 -0
  389. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  390. /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
  391. /package/dist/{chunk-2JNBPOZ2.js.map → chunk-GDYLZ6FR.js.map} +0 -0
  392. /package/dist/{chunk-DZPTR2XQ.js.map → chunk-GFAEKSFR.js.map} +0 -0
  393. /package/dist/{chunk-M7LA4QBY.js.map → chunk-GXVXBYRA.js.map} +0 -0
  394. /package/dist/{chunk-JGMJZBFV.js.map → chunk-HPNXS6W6.js.map} +0 -0
  395. /package/dist/{chunk-BFPF5N3H.js.map → chunk-HXFIILGF.js.map} +0 -0
  396. /package/dist/{chunk-CMNL3QSL.js.map → chunk-IN6U2BS5.js.map} +0 -0
  397. /package/dist/{chunk-BWRX322G.js.map → chunk-IUYXCTA7.js.map} +0 -0
  398. /package/dist/{chunk-LU57EEJW.js.map → chunk-JBWD3J5U.js.map} +0 -0
  399. /package/dist/{chunk-2V664H6O.js.map → chunk-JS44PBCM.js.map} +0 -0
  400. /package/dist/{chunk-XKTHJHSQ.js.map → chunk-JZCBMKR5.js.map} +0 -0
  401. /package/dist/{chunk-MXPVNDGO.js.map → chunk-K5QQXVG6.js.map} +0 -0
  402. /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
  403. /package/dist/{chunk-WUBGMV3G.js.map → chunk-KRAMCBIW.js.map} +0 -0
  404. /package/dist/{chunk-NDMPRT3Z.js.map → chunk-KW5YZELC.js.map} +0 -0
  405. /package/dist/{chunk-2RQR4TXV.js.map → chunk-LIBVANOF.js.map} +0 -0
  406. /package/dist/{chunk-M7L5A2AT.js.map → chunk-LMMBV7QC.js.map} +0 -0
  407. /package/dist/{chunk-PJBYVJKO.js.map → chunk-LXXIV7Y2.js.map} +0 -0
  408. /package/dist/{chunk-MZCTS3ZG.js.map → chunk-M2DOJBH7.js.map} +0 -0
  409. /package/dist/{chunk-WOPZ2P5Z.js.map → chunk-M4HRYLOA.js.map} +0 -0
  410. /package/dist/{chunk-JCYEDMOY.js.map → chunk-M6TP6UYD.js.map} +0 -0
  411. /package/dist/{chunk-KAUCVTAW.js.map → chunk-MLNXATFG.js.map} +0 -0
  412. /package/dist/{chunk-2NX7JTST.js.map → chunk-MPWOAIIH.js.map} +0 -0
  413. /package/dist/{chunk-RQLTCHUL.js.map → chunk-MWURUSRE.js.map} +0 -0
  414. /package/dist/{chunk-3EGL7XZO.js.map → chunk-N74WJ7B2.js.map} +0 -0
  415. /package/dist/{chunk-DRXTMDAG.js.map → chunk-N7FNCE7F.js.map} +0 -0
  416. /package/dist/{chunk-6CWAY4R7.js.map → chunk-NPLM7AFU.js.map} +0 -0
  417. /package/dist/{chunk-VECRSYQG.js.map → chunk-NRGQ5HJO.js.map} +0 -0
  418. /package/dist/{chunk-PPDNG7WC.js.map → chunk-NSP5PIQH.js.map} +0 -0
  419. /package/dist/{chunk-6HVLHIAB.js.map → chunk-P7OVFGMU.js.map} +0 -0
  420. /package/dist/{chunk-EX27VKU6.js.map → chunk-PDTNQLPC.js.map} +0 -0
  421. /package/dist/{chunk-ME5LVISI.js.map → chunk-PJCVI44H.js.map} +0 -0
  422. /package/dist/{chunk-LJCHDFLP.js.map → chunk-Q3PNQYAC.js.map} +0 -0
  423. /package/dist/{chunk-YFJHFEET.js.map → chunk-QS765SBL.js.map} +0 -0
  424. /package/dist/{chunk-FJ264QBP.js.map → chunk-QTDZ2NYC.js.map} +0 -0
  425. /package/dist/{chunk-Q6UZREJK.js.map → chunk-RKRX4C4H.js.map} +0 -0
  426. /package/dist/{chunk-GLZWOIU3.js.map → chunk-SJTSWG2Z.js.map} +0 -0
  427. /package/dist/{chunk-XM747YJF.js.map → chunk-TFWJXWL6.js.map} +0 -0
  428. /package/dist/{chunk-QFQ4UG3I.js.map → chunk-TXKQBQYW.js.map} +0 -0
  429. /package/dist/{chunk-BMOJFGVX.js.map → chunk-UALXWWGN.js.map} +0 -0
  430. /package/dist/{chunk-B44NFHWI.js.map → chunk-UNOAVHV4.js.map} +0 -0
  431. /package/dist/{chunk-TF5NZGPS.js.map → chunk-UQBOPCDK.js.map} +0 -0
  432. /package/dist/{chunk-5R2IF2KI.js.map → chunk-UXGXJUZ5.js.map} +0 -0
  433. /package/dist/{chunk-J6B6FK2X.js.map → chunk-VCOMACR6.js.map} +0 -0
  434. /package/dist/{chunk-WCGJK4NQ.js.map → chunk-VKQI47DJ.js.map} +0 -0
  435. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  436. /package/dist/{chunk-BCDECPVO.js.map → chunk-WV4UYDP6.js.map} +0 -0
  437. /package/dist/{chunk-VWJ56T63.js.map → chunk-WXJC2L66.js.map} +0 -0
  438. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  439. /package/dist/{chunk-FPANDKMQ.js.map → chunk-XC3H7KON.js.map} +0 -0
  440. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  441. /package/dist/{chunk-3SL52JKD.js.map → chunk-XVZA6VXC.js.map} +0 -0
  442. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  443. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  444. /package/dist/{chunk-XTVH2NAW.js.map → chunk-YECEEJUU.js.map} +0 -0
  445. /package/dist/{chunk-QR6W3FSW.js.map → chunk-Z5277JWF.js.map} +0 -0
  446. /package/dist/{chunk-CTHYR4QK.js.map → chunk-ZFNXJEUG.js.map} +0 -0
  447. /package/dist/{chunk-6Q56WDHG.js.map → chunk-ZLCBLDJO.js.map} +0 -0
  448. /package/dist/{chunk-KRACAE73.js.map → chunk-ZNRTHYYW.js.map} +0 -0
  449. /package/dist/{chunk-7AIPWNM7.js.map → chunk-ZSMA67FV.js.map} +0 -0
@@ -0,0 +1,51 @@
1
+ // Node-local warm-cache generation marker (`<stateDir>/embeddings.generation`).
2
+ // Two-phase publication barrier: in-flight before a publication's first
3
+ // destructive write, fresh unique value after its last write (PR #3176), so a
4
+ // completed stamp is only ever stable while generation bytes are unchanged.
5
+ // Single owner of the atomic protocol and logging — the store's own mutations
6
+ // and the offline-sync direct write/delete paths (issue #3182) share it.
7
+ // Plain fs always (tmp + rename): the marker is node-local raw bytes, never
8
+ // sync traffic or secure-store content.
9
+ import { randomUUID } from "node:crypto";
10
+ import { rename, rm, writeFile } from "node:fs/promises";
11
+ import path from "node:path";
12
+
13
+ import { log } from "./logger.js";
14
+
15
+ export const GENERATION_MARKER_BASENAME = "embeddings.generation";
16
+
17
+ /** Reserved in-flight marker value; never equals a completion marker. */
18
+ export const GENERATION_MARKER_IN_FLIGHT = "in-flight";
19
+
20
+ export function generationMarkerPath(stateDirAbs: string): string {
21
+ return path.join(stateDirAbs, GENERATION_MARKER_BASENAME);
22
+ }
23
+
24
+ /** Strict atomic marker write; recovery uses it for a fresh completed value. */
25
+ export async function writeGenerationMarkerRaw(markerAbs: string, value: string): Promise<void> {
26
+ const tempPath = `${markerAbs}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
27
+ try {
28
+ await writeFile(tempPath, value, "utf-8");
29
+ await rename(tempPath, markerAbs);
30
+ } catch (err) {
31
+ await rm(tempPath, { force: true }).catch(() => undefined);
32
+ throw err;
33
+ }
34
+ }
35
+
36
+ /** Barrier first half: strict, so bytes can never change under a stable stamp. */
37
+ export async function beginGenerationMarker(markerAbs: string): Promise<void> {
38
+ await writeGenerationMarkerRaw(markerAbs, GENERATION_MARKER_IN_FLIGHT);
39
+ }
40
+
41
+ /**
42
+ * Barrier second half: written after the last content write. Best-effort:
43
+ * a failure leaves the in-flight marker, whose stamp identityStamp()
44
+ * never repeats, so cached probes revalidate on every search until a
45
+ * publication completes.
46
+ */
47
+ export async function completeGenerationMarker(markerAbs: string): Promise<void> {
48
+ await writeGenerationMarkerRaw(markerAbs, randomUUID()).catch((err) => {
49
+ log.warn(`embedding index: could not write generation completion marker: ${err instanceof Error ? err.message : String(err)}`);
50
+ });
51
+ }
@@ -18,6 +18,13 @@ import { randomUUID } from "node:crypto";
18
18
  import { constants as bufferConstants } from "node:buffer";
19
19
  import { lstat, mkdir, mkdtemp, realpath, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
20
20
  import { log } from "./logger.js";
21
+ import {
22
+ GENERATION_MARKER_IN_FLIGHT,
23
+ beginGenerationMarker as writeGenerationMarkerInFlight,
24
+ completeGenerationMarker as writeGenerationMarkerComplete,
25
+ generationMarkerPath,
26
+ writeGenerationMarkerRaw,
27
+ } from "./embedding-generation-marker.js";
21
28
  import { readEnvVar } from "./runtime/env.js";
22
29
  import type { EmbeddingGenerationFence } from "./embedding-generation-lock.js";
23
30
 
@@ -166,21 +173,6 @@ function replacementBackupPath(shardDir: string): string {
166
173
  return path.join(path.dirname(shardDir), "embeddings.pre-replace.tmp");
167
174
  }
168
175
 
169
- /**
170
- * Warm-cache generation marker (`<stateDir>/embeddings.generation`).
171
- * Two-phase publication barrier: in-flight before a publication's first
172
- * destructive write, fresh unique value after its last write. A stamp is
173
- * therefore stable only for a completed generation — readers can never
174
- * cache a stamp that stays stable while generation bytes change, and an
175
- * in-flight stamp never repeats, so probes cached during a failed
176
- * completion keep revalidating (PR #3176 codex P2). Plain fs like the
177
- * status file.
178
- */
179
- const GENERATION_MARKER_BASENAME = "embeddings.generation";
180
-
181
- /** Reserved in-flight marker value; never equals a completion marker. */
182
- const GENERATION_MARKER_IN_FLIGHT = "in-flight";
183
-
184
176
  /**
185
177
  * Stable FNV-1a shard assignment: an entry always hashes to the same shard,
186
178
  * so an update or removal never needs a manifest to find its file.
@@ -535,34 +527,26 @@ export class EmbeddingIndexFileStore {
535
527
 
536
528
  /** Barrier first half: strict, so bytes can never change under a stable stamp. */
537
529
  private async beginGenerationMarker(): Promise<void> {
538
- await this.writeGenerationMarker(GENERATION_MARKER_IN_FLIGHT);
530
+ await writeGenerationMarkerInFlight(generationMarkerPath(path.dirname(this.shardDir)));
539
531
  }
540
532
 
541
533
  /**
542
- * Barrier second half: written after the last content write. Best-effort:
543
- * a failure leaves the in-flight marker, whose stamp identityStamp()
544
- * never repeats, so cached probes revalidate on every search until a
545
- * publication completes.
534
+ * Barrier second half: written after the last content write. Best-effort;
535
+ * the in-flight fallback semantics live in the shared marker module.
546
536
  */
547
537
  private async completeGenerationMarker(): Promise<void> {
548
- await this.writeGenerationMarker(randomUUID()).catch((err) => {
549
- log.warn(`embedding index: could not write generation completion marker: ${err instanceof Error ? err.message : String(err)}`);
550
- });
538
+ await writeGenerationMarkerComplete(generationMarkerPath(path.dirname(this.shardDir)));
551
539
  }
552
540
 
553
541
  private async writeGenerationMarker(value: string): Promise<void> {
554
- await this.writeAtomicFile(
555
- path.join(path.dirname(this.shardDir), GENERATION_MARKER_BASENAME),
556
- value,
557
- { bypassSecureIo: true },
558
- );
542
+ await writeGenerationMarkerRaw(generationMarkerPath(path.dirname(this.shardDir)), value);
559
543
  }
560
544
 
561
545
  /** Marker value, or null when absent/unreadable (pre-marker stores keep the stat stamp). */
562
546
  private async readGenerationMarker(): Promise<string | null> {
563
547
  try {
564
548
  const raw = await readFile(
565
- path.join(path.dirname(this.shardDir), GENERATION_MARKER_BASENAME),
549
+ generationMarkerPath(path.dirname(this.shardDir)),
566
550
  "utf-8",
567
551
  );
568
552
  const value = raw.trim();
@@ -0,0 +1,229 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { StorageManager, parseEntityFile } from "./storage.js";
7
+ import { sleep } from "./utils/sleep.js";
8
+
9
+ async function legacyStore() {
10
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-lazy-compat-"));
11
+ const seed = new StorageManager(dir);
12
+ const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
13
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
14
+ const journal = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
15
+ await rm(journal, { force: true });
16
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
17
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
18
+ const storage = new StorageManager(dir);
19
+ await storage.ensureDirectories({ waitForEntityMigration: false });
20
+ return { dir, id, journal, lock, storage };
21
+ }
22
+
23
+ test("canonical reads find a legacy file without a journal or migration", async () => {
24
+ const fixture = await legacyStore();
25
+ try {
26
+ assert.match(await fixture.storage.readEntity(fixture.id), /Keeps a synthetic task/);
27
+ await assert.rejects(() => readFile(fixture.journal), { code: "ENOENT" });
28
+ assert.ok(await readFile(fixture.lock));
29
+ } finally {
30
+ StorageManager.clearAllStaticCaches();
31
+ await rm(fixture.dir, { recursive: true, force: true });
32
+ }
33
+ });
34
+
35
+ test("ambiguous legacy canonical matches are refused rather than picking a claimant", async () => {
36
+ const fixture = await legacyStore();
37
+ try {
38
+ const raw = await readFile(path.join(fixture.dir, "entities", "legacy-person.md"));
39
+ await writeFile(path.join(fixture.dir, "entities", "other-legacy-person.md"), raw);
40
+ await assert.rejects(() => fixture.storage.readEntity(fixture.id), /ambiguous/i);
41
+ } finally {
42
+ StorageManager.clearAllStaticCaches();
43
+ await rm(fixture.dir, { recursive: true, force: true });
44
+ }
45
+ });
46
+
47
+ test("completed migration skips corpus reads on repeated misses in a fresh read-only store", async () => {
48
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-miss-"));
49
+ try {
50
+ const seed = new StorageManager(dir);
51
+ await seed.writeEntity("Existing Person", "person", ["Synthetic fact."]);
52
+ await seed.ensureDirectories();
53
+ const reader = new StorageManager(dir);
54
+ await reader.ensureDirectories({ waitForEntityMigration: false });
55
+ // Bracket access is TypeScript's typed escape hatch for the private reader.
56
+ const originalRead = reader["readStorageSecureFile"].bind(reader);
57
+ const reads: string[] = [];
58
+ reader["readStorageSecureFile"] = async (filePath: string) => {
59
+ reads.push(path.basename(filePath));
60
+ return originalRead(filePath);
61
+ };
62
+ assert.equal(await reader.readEntity("person-missing"), "");
63
+ assert.equal(await reader.readEntity("person-missing"), "");
64
+ assert.deepEqual(reads, ["person-missing.md", "person-missing.md"], "completed-store misses must not scan entity contents");
65
+ } finally {
66
+ StorageManager.clearAllStaticCaches();
67
+ await rm(dir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ test("an external entities change invalidates the amortized completion verdict on the same reader", async () => {
72
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-cache-"));
73
+ try {
74
+ const seed = new StorageManager(dir);
75
+ const id = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
76
+ await seed.ensureDirectories();
77
+ const reader = new StorageManager(dir);
78
+ await reader.ensureDirectories({ waitForEntityMigration: false });
79
+ const canonical = reader.normalizeEntityName("Café", "project");
80
+ // First miss computes and caches the completion verdict.
81
+ assert.equal(await reader.readEntity("project-missing"), "");
82
+ // An external rename changes the entities directory; the cached verdict
83
+ // must not keep hiding the now-legacy file from the same reader.
84
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-cafe.md"));
85
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
86
+ } finally {
87
+ StorageManager.clearAllStaticCaches();
88
+ await rm(dir, { recursive: true, force: true });
89
+ }
90
+ });
91
+
92
+ test("an external aliases change invalidates the amortized completion verdict on the same reader", async () => {
93
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-alias-cache-"));
94
+ try {
95
+ const seed = new StorageManager(dir);
96
+ const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
97
+ await seed.ensureDirectories();
98
+ const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
99
+ const journal = await readFile(journalPath, "utf8");
100
+ assert.equal(JSON.parse(journal).complete, true);
101
+
102
+ const reader = new StorageManager(dir);
103
+ await reader.ensureDirectories({ waitForEntityMigration: false });
104
+ // Warm amortized completion (and any same-reader completion memo) on a miss.
105
+ assert.equal(await reader.readEntity("project-missing"), "");
106
+
107
+ // Block migration repair so fingerprint drift stays visible to reads.
108
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
109
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
110
+
111
+ // External aliases.json is a getFingerprint input. It must invalidate a
112
+ // warm completed verdict without entities membership or mutation-version change.
113
+ // The entity file stays at previousId; only the alias-derived canonical id moves.
114
+ await mkdir(path.join(dir, "config"), { recursive: true });
115
+ await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
116
+ await reader.loadAliases();
117
+ const canonical = reader.normalizeEntityName("Café", "project");
118
+ assert.notEqual(canonical, previousId);
119
+
120
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
121
+ assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
122
+ assert.ok(await readFile(lock));
123
+ } finally {
124
+ StorageManager.clearAllStaticCaches();
125
+ await rm(dir, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+
130
+ for (const change of ["aliases", "raw legacy write"]) {
131
+ test(`stale completed journals do not hide canonical reads after ${change}`, async () => {
132
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-completion-"));
133
+ try {
134
+ const seed = new StorageManager(dir);
135
+ const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
136
+ await seed.ensureDirectories();
137
+ const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
138
+ const journal = await readFile(journalPath, "utf8");
139
+ assert.equal(JSON.parse(journal).complete, true);
140
+ if (change === "aliases") {
141
+ await mkdir(path.join(dir, "config"), { recursive: true });
142
+ await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
143
+ } else {
144
+ await rename(path.join(dir, "entities", `${previousId}.md`), path.join(dir, "entities", "legacy-cafe.md"));
145
+ await writeFile(path.join(dir, "state", "entity-canonical-id-reconcile.pending"), "synthetic raw writer\n");
146
+ }
147
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
148
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
149
+ const reader = new StorageManager(dir);
150
+ await reader.ensureDirectories({ waitForEntityMigration: false });
151
+ const canonical = reader.normalizeEntityName("Café", "project");
152
+ if (change === "aliases") assert.notEqual(canonical, previousId);
153
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
154
+ assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
155
+ assert.ok(await readFile(lock));
156
+ } finally {
157
+ StorageManager.clearAllStaticCaches();
158
+ await rm(dir, { recursive: true, force: true });
159
+ }
160
+ });
161
+ }
162
+
163
+ const mutations = [
164
+ {
165
+ name: "relationship",
166
+ run: (s: StorageManager, id: string) => s.addEntityRelationship(id, { target: "person-peer", label: "knows" }),
167
+ check: (raw: string) => assert.ok(parseEntityFile(raw).relationships.some((r) => r.label === "knows")),
168
+ },
169
+ {
170
+ name: "activity",
171
+ run: (s: StorageManager, id: string) => s.addEntityActivity(id, { date: "2026-01-01", note: "Synthetic activity" }, 10),
172
+ check: (raw: string) => assert.ok(parseEntityFile(raw).activity.some((a) => a.note === "Synthetic activity")),
173
+ },
174
+ {
175
+ name: "alias",
176
+ run: (s: StorageManager, id: string) => s.addEntityAlias(id, "synthetic-alias"),
177
+ check: (raw: string) => assert.ok(parseEntityFile(raw).aliases.includes("synthetic-alias")),
178
+ },
179
+ {
180
+ name: "synthesis",
181
+ run: (s: StorageManager, id: string) => s.updateEntitySynthesis(id, "Synthetic synthesis"),
182
+ check: (raw: string) => assert.equal(parseEntityFile(raw).synthesis, "Synthetic synthesis"),
183
+ },
184
+ {
185
+ name: "fragment merge",
186
+ run: (s: StorageManager) => s.mergeFragmentedEntities(),
187
+ check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
188
+ },
189
+ {
190
+ name: "timeline migration",
191
+ run: (s: StorageManager) => s.migrateEntityFilesToCompiledTruthTimeline(),
192
+ check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
193
+ },
194
+ ];
195
+
196
+ for (const caller of ["canonical", "legacy"]) {
197
+ for (const mutation of mutations) {
198
+ test(`${mutation.name} awaits migration for a ${caller} caller`, async () => {
199
+ const fixture = await legacyStore();
200
+ let settled = false;
201
+ let failure: Error | undefined;
202
+ const operation = mutation.run(fixture.storage, caller === "legacy" ? "legacy-person" : fixture.id).then(
203
+ () => { settled = true; },
204
+ (error: Error) => { failure = error; settled = true; },
205
+ );
206
+ let probeFailure: unknown;
207
+ try {
208
+ await sleep(100);
209
+ assert.equal(settled, false, "a public entity mutation must wait behind the migration lock");
210
+ } catch (error) {
211
+ probeFailure = error;
212
+ } finally {
213
+ await rm(fixture.lock, { force: true });
214
+ }
215
+ try {
216
+ await operation;
217
+ if (probeFailure) throw probeFailure;
218
+ assert.equal(failure, undefined);
219
+ const raw = await readFile(path.join(fixture.dir, "entities", `${fixture.id}.md`), "utf8");
220
+ mutation.check(raw);
221
+ const journal = JSON.parse(await readFile(fixture.journal, "utf8"));
222
+ assert.equal(journal.complete, true);
223
+ } finally {
224
+ StorageManager.clearAllStaticCaches();
225
+ await rm(fixture.dir, { recursive: true, force: true });
226
+ }
227
+ });
228
+ }
229
+ }
@@ -0,0 +1,148 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, rename, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { parseConfig } from "./config.js";
8
+ import { Orchestrator } from "./orchestrator.js";
9
+ import { StorageManager } from "./storage.js";
10
+
11
+ // #3240 follow-up: the host's onEntityMigrationComplete slot must fire once
12
+ // per migration pass that rewrote entities (never on no-op replays or ensure() no-ops), fail open on a
13
+ // throwing hook, reach router-created storages independent of auto-embed, and
14
+ // be inert after Orchestrator.destroy(). No direct QMD calls; the re-arm is
15
+ // observed through a requestQmdMaintenance spy.
16
+
17
+ async function legacyEntityStore(prefix: string): Promise<string> {
18
+ const dir = await mkdtemp(path.join(os.tmpdir(), prefix));
19
+ const seed = new StorageManager(dir);
20
+ const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
21
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
22
+ await rm(path.join(dir, "state", "entity-canonical-id-migration-v1.json"), { force: true });
23
+ return dir;
24
+ }
25
+
26
+ test("real storage fires the completion slot only for a pass that rewrote entities, and fails open on a throwing hook", async () => {
27
+ const dir = await legacyEntityStore("remnic-migration-complete-");
28
+ const emptyDir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-noop-"));
29
+ const throwingDir = await legacyEntityStore("remnic-migration-throwing-");
30
+ try {
31
+ const storage = new StorageManager(dir);
32
+ let completions = 0;
33
+ storage.onEntityMigrationComplete = () => {
34
+ completions += 1;
35
+ };
36
+ await storage.ensureDirectories();
37
+ assert.equal(completions, 1, "a migration pass that renamed a legacy entity must fire the completion slot");
38
+ await storage.ensureDirectories();
39
+ assert.equal(completions, 1, "a second ensureDirectories must not re-fire the slot");
40
+
41
+ const noop = new StorageManager(emptyDir);
42
+ let noopCompletions = 0;
43
+ noop.onEntityMigrationComplete = () => {
44
+ noopCompletions += 1;
45
+ };
46
+ await noop.ensureDirectories();
47
+ assert.equal(noopCompletions, 0, "a no-op replay must not ask search maintenance to run");
48
+
49
+ const throwing = new StorageManager(throwingDir);
50
+ let hookCalls = 0;
51
+ throwing.onEntityMigrationComplete = () => {
52
+ hookCalls += 1;
53
+ throw new Error("consumer hook exploded");
54
+ };
55
+ await throwing.ensureDirectories();
56
+ assert.equal(hookCalls, 1, "the throwing hook must still be invoked");
57
+ } finally {
58
+ StorageManager.clearAllStaticCaches();
59
+ await rm(dir, { recursive: true, force: true });
60
+ await rm(emptyDir, { recursive: true, force: true });
61
+ await rm(throwingDir, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("orchestrator wires completion with auto-embed off and blocks it from teardown admission", async () => {
66
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-complete-orch-"));
67
+ const orchestrator = new Orchestrator(
68
+ parseConfig({
69
+ memoryDir: dir,
70
+ workspaceDir: path.join(dir, "workspace"),
71
+ qmdEnabled: false,
72
+ qmdAutoEmbedEnabled: false,
73
+ namespacesEnabled: true,
74
+ namespaceCatalogEnabled: true,
75
+ defaultNamespace: "default",
76
+ sharedNamespace: "shared",
77
+ namespacePolicies: [],
78
+ embeddingFallbackEnabled: false,
79
+ identityContinuityEnabled: false,
80
+ transcriptEnabled: false,
81
+ hourlySummariesEnabled: false,
82
+ compoundingEnabled: false,
83
+ knowledgeIndexEnabled: false,
84
+ }),
85
+ );
86
+ // Test-only access to the Orchestrator's private wiring fields: bracket access
87
+ // is TypeScript's typed escape hatch for private members, so no assertion.
88
+ const internals = {
89
+ get storage() { return orchestrator["storage"]; },
90
+ get storageRouter() { return orchestrator["storageRouter"]; },
91
+ get maintenanceScheduler() { return orchestrator["maintenanceScheduler"]; },
92
+ set requestQmdMaintenance(fn: () => void) { orchestrator["requestQmdMaintenance"] = fn; },
93
+ };
94
+ try {
95
+ // Wiring must exist independent of auto-embed.
96
+ const onMigrationComplete = internals.storage.onEntityMigrationComplete;
97
+ assert.ok(
98
+ onMigrationComplete instanceof Function,
99
+ "the default storage must carry the migration completion slot without auto-embed",
100
+ );
101
+ const namespaceStorage = await internals.storageRouter.storageFor("proj-a");
102
+ const onNamespaceMigrationComplete = namespaceStorage.onEntityMigrationComplete;
103
+ assert.ok(
104
+ onNamespaceMigrationComplete instanceof Function,
105
+ "a router-created namespace storage must carry the slot without auto-embed",
106
+ );
107
+
108
+ const rearmRequests: string[] = [];
109
+ internals.requestQmdMaintenance = () => {
110
+ rearmRequests.push("rearm");
111
+ };
112
+ assert.ok(onMigrationComplete);
113
+ onMigrationComplete();
114
+ assert.ok(onNamespaceMigrationComplete);
115
+ onNamespaceMigrationComplete();
116
+ assert.deepEqual(rearmRequests, ["rearm", "rearm"], "a completion must re-arm QMD maintenance");
117
+
118
+ const { promise: disposeGate, resolve: releaseDispose } = Promise.withResolvers<void>();
119
+ const { promise: disposing, resolve: signalDispose } = Promise.withResolvers<void>();
120
+ const originalDispose = internals.maintenanceScheduler.dispose.bind(internals.maintenanceScheduler);
121
+ internals.maintenanceScheduler.dispose = async () => {
122
+ signalDispose();
123
+ await disposeGate;
124
+ await originalDispose();
125
+ };
126
+ const teardown = orchestrator.destroy();
127
+ try {
128
+ await disposing;
129
+ onMigrationComplete();
130
+ onNamespaceMigrationComplete();
131
+ assert.deepEqual(rearmRequests, ["rearm", "rearm"], "teardown admission must stop completion re-arms before drains");
132
+ } finally {
133
+ releaseDispose();
134
+ await teardown;
135
+ }
136
+ onMigrationComplete();
137
+ onNamespaceMigrationComplete();
138
+ assert.deepEqual(
139
+ rearmRequests,
140
+ ["rearm", "rearm"],
141
+ "a completion after destroy must not re-arm maintenance",
142
+ );
143
+ } finally {
144
+ await orchestrator.destroy().catch(() => undefined);
145
+ StorageManager.clearAllStaticCaches();
146
+ await rm(dir, { recursive: true, force: true });
147
+ }
148
+ });
package/src/index.ts CHANGED
@@ -657,7 +657,7 @@ export {
657
657
  type ParsedBriefingWindow,
658
658
  type BriefingFormatValue,
659
659
  } from "./briefing.js";
660
-
660
+ export { withBriefingDeadline, BriefingTimeoutError, BRIEFING_DEADLINE_MS } from "./briefing-deadline.js";
661
661
  // ---------------------------------------------------------------------------
662
662
  // Binary lifecycle management (#367)
663
663
  // ---------------------------------------------------------------------------
@@ -880,7 +880,7 @@ export {
880
880
  type OfflineSyncSnapshot,
881
881
  type OfflineSyncState,
882
882
  } from "./offline-sync.js";
883
- export { embeddingGenerationMembership, divergedEmbeddingGenerationDeferrals } from "./offline-sync-embedding-generation.js";
883
+ export { advanceEmbeddingGenerationMarker, embeddingGenerationMembership, divergedEmbeddingGenerationDeferrals } from "./offline-sync-embedding-generation.js";
884
884
  export { EmbeddingIndexCapacityError, EmbeddingIndexFileStore, EmbeddingIndexStorageError, parseEmbeddingIndexDocument, serializeEmbeddingShard, shardEntriesForIndex, shardIndexOf, validateShardMembership } from "./embedding-index-storage.js";
885
885
 
886
886
  // ---------------------------------------------------------------------------
@@ -21,7 +21,7 @@
21
21
 
22
22
  import type { StorageManager } from "../index.js";
23
23
  import { log } from "../logger.js";
24
- import type { MemoryFile } from "../types.js";
24
+ import type { BriefingResult, MemoryFile } from "../types.js";
25
25
  import type { WearableConversation } from "../wearables/types.js";
26
26
  import { localDayKey, placeDurations } from "./intervals.js";
27
27
  import {
@@ -294,23 +294,25 @@ export async function briefingLocationSection(
294
294
  );
295
295
  }
296
296
 
297
- export async function applyBriefingLocationContext<
298
- T extends { markdown: string; json: Record<string, unknown> },
299
- >(
300
- result: T,
297
+ /**
298
+ * Append the opt-in location section to a briefing. The section is read BEFORE
299
+ * `build` runs: buildBriefing drains correction notifications at most once, so
300
+ * a deadline during this read must not lose them.
301
+ */
302
+ export async function buildWithBriefingLocation<T extends Pick<BriefingResult, "markdown" | "json">>(
301
303
  includeLocation: boolean | undefined,
302
304
  memoryDir: string,
303
305
  windowStartUtc: string,
304
306
  location: LocationConfig,
307
+ build: () => Promise<T>,
305
308
  ): Promise<T> {
306
- if (includeLocation !== true || !locationTaggingEnabled(location)) return result;
307
- const section = await briefingLocationSection(memoryDir, windowStartUtc, location);
309
+ const section =
310
+ includeLocation === true && locationTaggingEnabled(location)
311
+ ? await briefingLocationSection(memoryDir, windowStartUtc, location)
312
+ : null;
313
+ const result = await build();
308
314
  if (!section) return result;
309
- return {
310
- ...result,
311
- markdown: `${result.markdown}\n\n${section}\n`,
312
- json: { ...result.json, locationContext: section },
313
- };
315
+ return { ...result, markdown: `${result.markdown}\n\n${section}\n`, json: { ...result.json, locationContext: section } };
314
316
  }
315
317
 
316
318
  /**
@@ -0,0 +1,58 @@
1
+ import assert from "node:assert/strict";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import test from "node:test";
7
+ import { withEmbeddingGenerationLock } from "./embedding-generation-lock.js";
8
+ import { advanceEmbeddingGenerationMarker, embeddingGenerationMembership } from "./offline-sync-embedding-generation.js";
9
+
10
+ // Issue #3182 review: a peer can publish a newer shard while converge waits
11
+ // for the generation lock; the stale pre-lock digest must not delete it.
12
+ test("a stale-digest deletion is rejected under the generation lock", async (t) => {
13
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-3182-delete-digest-"));
14
+ t.after(() => rm(root, { recursive: true, force: true }));
15
+ const stateDir = path.join(root, "state");
16
+ const relative = "state/embeddings/shard-0000.json";
17
+ const target = path.join(root, relative);
18
+ const marker = path.join(stateDir, "embeddings.generation");
19
+ await mkdir(path.dirname(target), { recursive: true });
20
+ await writeFile(target, "older shard");
21
+ await writeFile(marker, "peer completed generation");
22
+ const staleSha256 = createHash("sha256").update("older shard").digest("hex");
23
+
24
+ let signalHeld: () => void;
25
+ const held = new Promise<void>((resolve) => { signalHeld = resolve; });
26
+ let releaseWriter: (() => void) | undefined;
27
+ const release = new Promise<void>((resolve) => { releaseWriter = resolve; });
28
+ assert.ok(releaseWriter);
29
+ const writer = withEmbeddingGenerationLock(stateDir, async () => {
30
+ signalHeld();
31
+ await release;
32
+ await writeFile(target, "newer peer shard");
33
+ });
34
+ await held;
35
+
36
+ let validated = false;
37
+ const deletion = advanceEmbeddingGenerationMarker(
38
+ root,
39
+ embeddingGenerationMembership(relative),
40
+ async () => {
41
+ assert.fail("deletion must never run when the digest is stale");
42
+ },
43
+ async () => {
44
+ validated = true;
45
+ if ((await readFile(target, "utf8")) !== "older shard") {
46
+ throw new Error("converge file changed before deletion");
47
+ }
48
+ },
49
+ );
50
+ const rejected = assert.rejects(deletion, /changed before deletion/);
51
+ releaseWriter();
52
+ await writer;
53
+ await rejected;
54
+
55
+ assert.equal(validated, true, "the digest precondition must run inside the lock");
56
+ assert.equal(await readFile(target, "utf8"), "newer peer shard");
57
+ assert.equal(await readFile(marker, "utf8"), "peer completed generation");
58
+ });