@remnic/core 10.0.28 → 10.0.30

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 (330) hide show
  1. package/dist/access-admin-ops-surface.js +47 -46
  2. package/dist/access-authorization-probe.js +48 -47
  3. package/dist/access-boundary.js +47 -46
  4. package/dist/access-citation-namespace.js +3 -2
  5. package/dist/access-cli.js +96 -95
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +47 -46
  8. package/dist/access-http-briefing.js +48 -47
  9. package/dist/access-http-query.js +49 -48
  10. package/dist/access-http.js +65 -64
  11. package/dist/access-identity-continuity-surface.js +47 -46
  12. package/dist/access-lcm-surface.js +47 -46
  13. package/dist/access-mcp.js +57 -56
  14. package/dist/access-namespace-preflight.js +47 -46
  15. package/dist/access-observe-write-category.js +11 -11
  16. package/dist/access-observe-write-surface.js +47 -46
  17. package/dist/access-offline-manifest.js +10 -10
  18. package/dist/access-operations-batch.js +49 -48
  19. package/dist/access-operations.js +56 -55
  20. package/dist/access-recall-concurrency.js +47 -46
  21. package/dist/access-recall-response.js +47 -46
  22. package/dist/access-recall-surface.js +47 -46
  23. package/dist/access-schema.js +6 -5
  24. package/dist/access-service.js +47 -46
  25. package/dist/access-surface-catalog.js +49 -48
  26. package/dist/access-surface-catalog.js.map +1 -1
  27. package/dist/active-recall.js +23 -22
  28. package/dist/active-recall.js.map +1 -1
  29. package/dist/{auto-sync-UQESPLCB.js → auto-sync-MX2AHVPQ.js} +8 -8
  30. package/dist/briefing.js +19 -18
  31. package/dist/buffer.js +8 -8
  32. package/dist/{capsule-merge-LGYURS3C.js → capsule-merge-LGYZAMO4.js} +3 -3
  33. package/dist/causal-consolidation.js +21 -20
  34. package/dist/causal-consolidation.js.map +1 -1
  35. package/dist/{chunk-2TGDT37F.js → chunk-27OJ5G3D.js} +13 -13
  36. package/dist/{chunk-5LRV557D.js → chunk-2FIJQNL7.js} +2 -2
  37. package/dist/{chunk-ETPIYK4J.js → chunk-2VBDSZ7U.js} +2 -2
  38. package/dist/{chunk-QLI74KWK.js → chunk-2XVLY424.js} +3 -3
  39. package/dist/{chunk-P55FACBG.js → chunk-3BOQPLCF.js} +6 -6
  40. package/dist/{chunk-3LRBJQZE.js → chunk-3CHHQXAR.js} +2 -2
  41. package/dist/{chunk-3GYSZHMT.js → chunk-3CZRBTJK.js} +2 -2
  42. package/dist/{chunk-HFSPY26A.js → chunk-3IXNL6SK.js} +2 -2
  43. package/dist/{chunk-4DQ4VEIQ.js → chunk-3SN2D43X.js} +2 -2
  44. package/dist/{chunk-QMYB3N7X.js → chunk-4HNINU7Q.js} +28 -28
  45. package/dist/{chunk-LW6UHNWA.js → chunk-4HYP4OKH.js} +2 -2
  46. package/dist/{chunk-GKJJNO7C.js → chunk-4OE4UE2Y.js} +6 -6
  47. package/dist/{chunk-5SMR542T.js → chunk-5GT4ESLR.js} +2 -2
  48. package/dist/{chunk-DWL6YIZH.js → chunk-5JI3XWI2.js} +2 -2
  49. package/dist/{chunk-PDN6MRWF.js → chunk-5NPZFXQH.js} +2 -2
  50. package/dist/{chunk-Y6IF6H2U.js → chunk-5SMHQW45.js} +2 -2
  51. package/dist/{chunk-PRUXE3RC.js → chunk-654GT5UP.js} +2 -2
  52. package/dist/{chunk-3E6DGSGZ.js → chunk-6MC7UQ6D.js} +2 -2
  53. package/dist/{chunk-655FVTCD.js → chunk-74IN6PV5.js} +2 -2
  54. package/dist/{chunk-XOSCGZEV.js → chunk-7FZ4YVOH.js} +2 -2
  55. package/dist/{chunk-MWWAQXZ6.js → chunk-7HFTCBCZ.js} +2 -2
  56. package/dist/{chunk-NDZ7APNY.js → chunk-7VB3Y5OL.js} +3 -3
  57. package/dist/{chunk-YEF5AAWN.js → chunk-A54E2ZTD.js} +2 -2
  58. package/dist/{chunk-DEY7B77Y.js → chunk-AHCELJMH.js} +2 -2
  59. package/dist/{chunk-PIWFNPQT.js → chunk-AL3TCNSJ.js} +4 -4
  60. package/dist/{chunk-KNWC2EPP.js → chunk-AUYFUBBE.js} +5 -5
  61. package/dist/{chunk-XN6ABQGM.js → chunk-BIE3ZH3N.js} +27 -27
  62. package/dist/{chunk-A3IURDU2.js → chunk-C2AOL6MK.js} +3 -3
  63. package/dist/{chunk-J4PILQJG.js → chunk-C2QVDLQV.js} +2 -2
  64. package/dist/{chunk-RKB4JZEM.js → chunk-CKR7LXEK.js} +67 -45
  65. package/dist/chunk-CKR7LXEK.js.map +1 -0
  66. package/dist/{chunk-AWLQNLTM.js → chunk-CSGFGQBI.js} +2 -2
  67. package/dist/{chunk-HZKDYR3Z.js → chunk-D4RV54HH.js} +2 -2
  68. package/dist/{chunk-OKNB2II7.js → chunk-DDLRKPR2.js} +6 -6
  69. package/dist/{chunk-3X3PNU3T.js → chunk-DLH3ECZP.js} +5 -5
  70. package/dist/chunk-DNIG3JME.js +892 -0
  71. package/dist/chunk-DNIG3JME.js.map +1 -0
  72. package/dist/{chunk-SA6GMZJY.js → chunk-DP3TSAYT.js} +2 -2
  73. package/dist/{chunk-XAJH5H3Z.js → chunk-FN6OFDBZ.js} +2 -2
  74. package/dist/{chunk-SUZG7UUK.js → chunk-G6DAE2LT.js} +2 -2
  75. package/dist/{chunk-AUPL775F.js → chunk-GSKW64NB.js} +2 -2
  76. package/dist/{chunk-3Z5LSDKR.js → chunk-GWB7ZD2A.js} +2 -2
  77. package/dist/{chunk-XOWR3I2W.js → chunk-HG2VVIKI.js} +9 -9
  78. package/dist/{chunk-23ZQPP2G.js → chunk-HOSJESKO.js} +3 -3
  79. package/dist/{chunk-EU6IRUCY.js → chunk-IDPZCHET.js} +2 -2
  80. package/dist/{chunk-3EE7EHFX.js → chunk-IO2UQVR2.js} +2 -2
  81. package/dist/{chunk-BWXKJDDH.js → chunk-ISMZ5UNE.js} +3 -3
  82. package/dist/{chunk-SX4QDWXK.js → chunk-IWUFKS4J.js} +2 -2
  83. package/dist/{chunk-MIIZAEJD.js → chunk-IY7DGT2Z.js} +2 -2
  84. package/dist/{chunk-STBX5X6B.js → chunk-JAYNUYSJ.js} +2 -2
  85. package/dist/{chunk-GV2V4CX6.js → chunk-K2REHR42.js} +2 -2
  86. package/dist/{chunk-NRG67KMO.js → chunk-KFKFK6G6.js} +10 -10
  87. package/dist/{chunk-FDPE6OIK.js → chunk-LDEKVBVO.js} +4 -4
  88. package/dist/{chunk-C2J4LBNB.js → chunk-M236YHUH.js} +5 -5
  89. package/dist/{chunk-L5VCT7LU.js → chunk-MCLZM5G7.js} +2 -2
  90. package/dist/{chunk-7Z3DLNSD.js → chunk-MGKH5KO3.js} +2 -2
  91. package/dist/{chunk-PUDYANAX.js → chunk-MMBLOJJB.js} +2 -2
  92. package/dist/{chunk-EFHNFTWB.js → chunk-MND4YJSX.js} +2 -2
  93. package/dist/{chunk-BQOGQULP.js → chunk-MZZEMPCI.js} +2 -2
  94. package/dist/{chunk-CQBNFDSG.js → chunk-NAPHQ2MG.js} +2 -2
  95. package/dist/{chunk-B6VPA3PF.js → chunk-NB2YX6X2.js} +6 -6
  96. package/dist/{chunk-PCSPXMPD.js → chunk-NCPR2FOJ.js} +3 -3
  97. package/dist/{chunk-67F6SBCJ.js → chunk-NOMHXZD7.js} +2 -2
  98. package/dist/{chunk-LWFWZTWX.js → chunk-NQV6G6MU.js} +2 -2
  99. package/dist/{chunk-JSFU7AFZ.js → chunk-OANSZGAG.js} +2 -2
  100. package/dist/{chunk-364M2T3D.js → chunk-OIIAIY7J.js} +3 -3
  101. package/dist/{chunk-IP5QGSNH.js → chunk-OXX4ORRL.js} +15 -15
  102. package/dist/{chunk-RTTPBBAN.js → chunk-OXYXMGRH.js} +4 -4
  103. package/dist/{chunk-K7BN34IA.js → chunk-PL26BCRN.js} +5 -5
  104. package/dist/{chunk-PM72NSEW.js → chunk-PYCTZ2HX.js} +2 -2
  105. package/dist/{chunk-P3NXZAV7.js → chunk-PZEADMX4.js} +2 -2
  106. package/dist/{chunk-QPH52AZB.js → chunk-QNDOTDJK.js} +2 -2
  107. package/dist/{chunk-LZACDIM2.js → chunk-RBGRQEW3.js} +4 -4
  108. package/dist/{chunk-V4JZCTMK.js → chunk-TKBW4YLS.js} +2 -2
  109. package/dist/{chunk-4D2YH47Z.js → chunk-TRW7N4FS.js} +2 -2
  110. package/dist/{chunk-XALR4DOQ.js → chunk-TTIEXXL3.js} +365 -1108
  111. package/dist/chunk-TTIEXXL3.js.map +1 -0
  112. package/dist/{chunk-OQJKY3Z5.js → chunk-UAAAXSKQ.js} +2 -2
  113. package/dist/{chunk-4LJSCW24.js → chunk-UM73Q46J.js} +2 -2
  114. package/dist/{chunk-7EJCAFMS.js → chunk-UTXYOWDR.js} +2 -2
  115. package/dist/{chunk-OGOADX26.js → chunk-UUYAW3JH.js} +2 -2
  116. package/dist/{chunk-UTGHW2Y5.js → chunk-UVUBLLM3.js} +3 -3
  117. package/dist/{chunk-4GGTC2AX.js → chunk-V4FI2TG6.js} +3 -3
  118. package/dist/{chunk-MO6N4QYY.js → chunk-VARNSLUW.js} +4 -4
  119. package/dist/{chunk-RQUF3FXK.js → chunk-VEB7NEON.js} +4 -4
  120. package/dist/{chunk-SSFO7B7H.js → chunk-VLBFSD7B.js} +2 -2
  121. package/dist/{chunk-NZFFGO5O.js → chunk-VXWOJPSY.js} +36 -3
  122. package/dist/chunk-VXWOJPSY.js.map +1 -0
  123. package/dist/{chunk-JNIVZNIB.js → chunk-WKVL2G7X.js} +4 -4
  124. package/dist/{chunk-KJXP6RUG.js → chunk-WNEVEMJ3.js} +4 -4
  125. package/dist/{chunk-I4MYLDRY.js → chunk-WQIYGYY6.js} +2 -2
  126. package/dist/{chunk-GQJKVQJR.js → chunk-WYLBEJQI.js} +2 -2
  127. package/dist/{chunk-JCPR47NY.js → chunk-XNROYP5E.js} +2 -2
  128. package/dist/{chunk-XNAOCY67.js → chunk-XVZIBMSY.js} +3 -3
  129. package/dist/{chunk-TZZFGR4E.js → chunk-YGVZ4QOZ.js} +4 -4
  130. package/dist/{chunk-C5JXYJHN.js → chunk-YZTFQBYY.js} +4 -4
  131. package/dist/{chunk-JTLXVXYH.js → chunk-ZCRDYQL6.js} +2 -2
  132. package/dist/{chunk-MAAL6OZV.js → chunk-ZG3HZZ6K.js} +90 -36
  133. package/dist/chunk-ZG3HZZ6K.js.map +1 -0
  134. package/dist/{chunk-CRVL6SLC.js → chunk-ZXTAHSQU.js} +2 -2
  135. package/dist/cli.js +80 -79
  136. package/dist/coding/export-okf-codegraph.js +20 -19
  137. package/dist/coding/export-okf-codegraph.js.map +1 -1
  138. package/dist/compounding/engine.js +22 -21
  139. package/dist/config.js +23 -22
  140. package/dist/connectors/codex-materialize-runner.js +20 -19
  141. package/dist/connectors/codex-materialize.js +2 -2
  142. package/dist/connectors/index.js +20 -19
  143. package/dist/consolidation-provenance-check.js +3 -3
  144. package/dist/consolidation-undo.js +2 -2
  145. package/dist/contradiction/index.js +6 -5
  146. package/dist/convergence-refresh.js +21 -20
  147. package/dist/corpus-watermark.js +21 -20
  148. package/dist/deep-recall-http-glue.js +7 -6
  149. package/dist/entity-canonical-id-migration-GD5MPFUF.js +28 -0
  150. package/dist/entity-retrieval.js +19 -18
  151. package/dist/explicit-capture.js +8 -7
  152. package/dist/external-wiki-access.js +48 -47
  153. package/dist/extraction.js +24 -23
  154. package/dist/harmonic-retrieval.js +20 -19
  155. package/dist/harmonic-source-lookup.js +3 -2
  156. package/dist/index.js +96 -95
  157. package/dist/location/index.js +22 -21
  158. package/dist/maintenance/memory-governance.js +19 -18
  159. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +19 -18
  160. package/dist/maintenance/rebuild-memory-projection-runner.js +21 -20
  161. package/dist/maintenance/rebuild-memory-projection.js +20 -19
  162. package/dist/memory-browse-operations.js +48 -47
  163. package/dist/memory-subject.js +4 -3
  164. package/dist/namespaces/migrate.js +20 -19
  165. package/dist/namespaces/search.js +7 -7
  166. package/dist/namespaces/storage.js +19 -18
  167. package/dist/offline-sync-file-io.js +3 -2
  168. package/dist/offline-sync-impression-drain.js +4 -4
  169. package/dist/offline-sync-snapshot-stream.js +6 -5
  170. package/dist/offline-sync-upload-staging.js +4 -3
  171. package/dist/offline-sync.js +5 -4
  172. package/dist/operator-doctor-corpus.js +22 -21
  173. package/dist/operator-toolkit.js +33 -32
  174. package/dist/orchestration/maintenance.js +32 -31
  175. package/dist/orchestration/orchestrator-init.d.ts +3 -0
  176. package/dist/orchestration/orchestrator-init.d.ts.map +1 -1
  177. package/dist/orchestrator.d.ts.map +1 -1
  178. package/dist/orchestrator.js +96 -95
  179. package/dist/page-versioning.js +2 -2
  180. package/dist/recall-navigation-operations.js +48 -47
  181. package/dist/recall-navigation.js +47 -46
  182. package/dist/recall-state.js +2 -2
  183. package/dist/reconcile/manifest.js +4 -3
  184. package/dist/{resolution-ZU5BIAJA.js → resolution-AMYTPT5X.js} +7 -6
  185. package/dist/resolution-AMYTPT5X.js.map +1 -0
  186. package/dist/resume-bundles.js +24 -23
  187. package/dist/retrieval-agents.js +3 -2
  188. package/dist/salvage-envelope.js +6 -5
  189. package/dist/search/document-scanner.js +2 -2
  190. package/dist/search/factory.js +6 -6
  191. package/dist/search/index.js +6 -6
  192. package/dist/search/lancedb-backend.js +3 -3
  193. package/dist/search/meilisearch-backend.js +3 -3
  194. package/dist/search/orama-backend.js +3 -3
  195. package/dist/secure-store/entity-file-read.d.ts +9 -0
  196. package/dist/secure-store/entity-file-read.d.ts.map +1 -0
  197. package/dist/secure-store/index.js +3 -2
  198. package/dist/secure-store/secure-fs.d.ts.map +1 -1
  199. package/dist/semantic-consolidation.js +21 -20
  200. package/dist/semantic-rule-promotion.js +19 -18
  201. package/dist/semantic-rule-verifier.js +19 -18
  202. package/dist/shared-context/manager.js +2 -2
  203. package/dist/source-agent-qualifier.js +23 -22
  204. package/dist/standup/build.js +19 -18
  205. package/dist/storage/entity-canonical-id-migration-runner.d.ts +12 -0
  206. package/dist/storage/entity-canonical-id-migration-runner.d.ts.map +1 -1
  207. package/dist/storage/entity-canonical-id-migration.d.ts +1 -0
  208. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  209. package/dist/storage/entity-canonical-id-references.d.ts +14 -0
  210. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  211. package/dist/storage/entity-store.d.ts +4 -0
  212. package/dist/storage/entity-store.d.ts.map +1 -1
  213. package/dist/storage.d.ts +7 -2
  214. package/dist/storage.d.ts.map +1 -1
  215. package/dist/storage.js +18 -17
  216. package/dist/subject-config.js +5 -4
  217. package/dist/support-passport/index.js +50 -49
  218. package/dist/transfer/capsule-import.js +3 -3
  219. package/dist/transfer/export-okf.js +20 -19
  220. package/dist/verified-recall.js +19 -18
  221. package/dist/who-knows-cli.js +20 -19
  222. package/dist/who-knows.js +19 -18
  223. package/dist/write-envelope.js +5 -4
  224. package/package.json +2 -2
  225. package/src/orchestration/orchestrator-init.ts +26 -3
  226. package/src/orchestrator.ts +1 -0
  227. package/src/secure-store/entity-file-read.ts +55 -0
  228. package/src/secure-store/secure-fs.ts +2 -1
  229. package/src/storage/entity-canonical-id-migration-runner.ts +36 -0
  230. package/src/storage/entity-canonical-id-migration.ts +1 -1
  231. package/src/storage/entity-canonical-id-references.ts +57 -2
  232. package/src/storage/entity-store.ts +138 -25
  233. package/src/storage.ts +15 -15
  234. package/dist/chunk-MAAL6OZV.js.map +0 -1
  235. package/dist/chunk-NZFFGO5O.js.map +0 -1
  236. package/dist/chunk-RKB4JZEM.js.map +0 -1
  237. package/dist/chunk-XALR4DOQ.js.map +0 -1
  238. /package/dist/{auto-sync-UQESPLCB.js.map → auto-sync-MX2AHVPQ.js.map} +0 -0
  239. /package/dist/{capsule-merge-LGYURS3C.js.map → capsule-merge-LGYZAMO4.js.map} +0 -0
  240. /package/dist/{chunk-2TGDT37F.js.map → chunk-27OJ5G3D.js.map} +0 -0
  241. /package/dist/{chunk-5LRV557D.js.map → chunk-2FIJQNL7.js.map} +0 -0
  242. /package/dist/{chunk-ETPIYK4J.js.map → chunk-2VBDSZ7U.js.map} +0 -0
  243. /package/dist/{chunk-QLI74KWK.js.map → chunk-2XVLY424.js.map} +0 -0
  244. /package/dist/{chunk-P55FACBG.js.map → chunk-3BOQPLCF.js.map} +0 -0
  245. /package/dist/{chunk-3LRBJQZE.js.map → chunk-3CHHQXAR.js.map} +0 -0
  246. /package/dist/{chunk-3GYSZHMT.js.map → chunk-3CZRBTJK.js.map} +0 -0
  247. /package/dist/{chunk-HFSPY26A.js.map → chunk-3IXNL6SK.js.map} +0 -0
  248. /package/dist/{chunk-4DQ4VEIQ.js.map → chunk-3SN2D43X.js.map} +0 -0
  249. /package/dist/{chunk-QMYB3N7X.js.map → chunk-4HNINU7Q.js.map} +0 -0
  250. /package/dist/{chunk-LW6UHNWA.js.map → chunk-4HYP4OKH.js.map} +0 -0
  251. /package/dist/{chunk-GKJJNO7C.js.map → chunk-4OE4UE2Y.js.map} +0 -0
  252. /package/dist/{chunk-5SMR542T.js.map → chunk-5GT4ESLR.js.map} +0 -0
  253. /package/dist/{chunk-DWL6YIZH.js.map → chunk-5JI3XWI2.js.map} +0 -0
  254. /package/dist/{chunk-PDN6MRWF.js.map → chunk-5NPZFXQH.js.map} +0 -0
  255. /package/dist/{chunk-Y6IF6H2U.js.map → chunk-5SMHQW45.js.map} +0 -0
  256. /package/dist/{chunk-PRUXE3RC.js.map → chunk-654GT5UP.js.map} +0 -0
  257. /package/dist/{chunk-3E6DGSGZ.js.map → chunk-6MC7UQ6D.js.map} +0 -0
  258. /package/dist/{chunk-655FVTCD.js.map → chunk-74IN6PV5.js.map} +0 -0
  259. /package/dist/{chunk-XOSCGZEV.js.map → chunk-7FZ4YVOH.js.map} +0 -0
  260. /package/dist/{chunk-MWWAQXZ6.js.map → chunk-7HFTCBCZ.js.map} +0 -0
  261. /package/dist/{chunk-NDZ7APNY.js.map → chunk-7VB3Y5OL.js.map} +0 -0
  262. /package/dist/{chunk-YEF5AAWN.js.map → chunk-A54E2ZTD.js.map} +0 -0
  263. /package/dist/{chunk-DEY7B77Y.js.map → chunk-AHCELJMH.js.map} +0 -0
  264. /package/dist/{chunk-PIWFNPQT.js.map → chunk-AL3TCNSJ.js.map} +0 -0
  265. /package/dist/{chunk-KNWC2EPP.js.map → chunk-AUYFUBBE.js.map} +0 -0
  266. /package/dist/{chunk-XN6ABQGM.js.map → chunk-BIE3ZH3N.js.map} +0 -0
  267. /package/dist/{chunk-A3IURDU2.js.map → chunk-C2AOL6MK.js.map} +0 -0
  268. /package/dist/{chunk-J4PILQJG.js.map → chunk-C2QVDLQV.js.map} +0 -0
  269. /package/dist/{chunk-AWLQNLTM.js.map → chunk-CSGFGQBI.js.map} +0 -0
  270. /package/dist/{chunk-HZKDYR3Z.js.map → chunk-D4RV54HH.js.map} +0 -0
  271. /package/dist/{chunk-OKNB2II7.js.map → chunk-DDLRKPR2.js.map} +0 -0
  272. /package/dist/{chunk-3X3PNU3T.js.map → chunk-DLH3ECZP.js.map} +0 -0
  273. /package/dist/{chunk-SA6GMZJY.js.map → chunk-DP3TSAYT.js.map} +0 -0
  274. /package/dist/{chunk-XAJH5H3Z.js.map → chunk-FN6OFDBZ.js.map} +0 -0
  275. /package/dist/{chunk-SUZG7UUK.js.map → chunk-G6DAE2LT.js.map} +0 -0
  276. /package/dist/{chunk-AUPL775F.js.map → chunk-GSKW64NB.js.map} +0 -0
  277. /package/dist/{chunk-3Z5LSDKR.js.map → chunk-GWB7ZD2A.js.map} +0 -0
  278. /package/dist/{chunk-XOWR3I2W.js.map → chunk-HG2VVIKI.js.map} +0 -0
  279. /package/dist/{chunk-23ZQPP2G.js.map → chunk-HOSJESKO.js.map} +0 -0
  280. /package/dist/{chunk-EU6IRUCY.js.map → chunk-IDPZCHET.js.map} +0 -0
  281. /package/dist/{chunk-3EE7EHFX.js.map → chunk-IO2UQVR2.js.map} +0 -0
  282. /package/dist/{chunk-BWXKJDDH.js.map → chunk-ISMZ5UNE.js.map} +0 -0
  283. /package/dist/{chunk-SX4QDWXK.js.map → chunk-IWUFKS4J.js.map} +0 -0
  284. /package/dist/{chunk-MIIZAEJD.js.map → chunk-IY7DGT2Z.js.map} +0 -0
  285. /package/dist/{chunk-STBX5X6B.js.map → chunk-JAYNUYSJ.js.map} +0 -0
  286. /package/dist/{chunk-GV2V4CX6.js.map → chunk-K2REHR42.js.map} +0 -0
  287. /package/dist/{chunk-NRG67KMO.js.map → chunk-KFKFK6G6.js.map} +0 -0
  288. /package/dist/{chunk-FDPE6OIK.js.map → chunk-LDEKVBVO.js.map} +0 -0
  289. /package/dist/{chunk-C2J4LBNB.js.map → chunk-M236YHUH.js.map} +0 -0
  290. /package/dist/{chunk-L5VCT7LU.js.map → chunk-MCLZM5G7.js.map} +0 -0
  291. /package/dist/{chunk-7Z3DLNSD.js.map → chunk-MGKH5KO3.js.map} +0 -0
  292. /package/dist/{chunk-PUDYANAX.js.map → chunk-MMBLOJJB.js.map} +0 -0
  293. /package/dist/{chunk-EFHNFTWB.js.map → chunk-MND4YJSX.js.map} +0 -0
  294. /package/dist/{chunk-BQOGQULP.js.map → chunk-MZZEMPCI.js.map} +0 -0
  295. /package/dist/{chunk-CQBNFDSG.js.map → chunk-NAPHQ2MG.js.map} +0 -0
  296. /package/dist/{chunk-B6VPA3PF.js.map → chunk-NB2YX6X2.js.map} +0 -0
  297. /package/dist/{chunk-PCSPXMPD.js.map → chunk-NCPR2FOJ.js.map} +0 -0
  298. /package/dist/{chunk-67F6SBCJ.js.map → chunk-NOMHXZD7.js.map} +0 -0
  299. /package/dist/{chunk-LWFWZTWX.js.map → chunk-NQV6G6MU.js.map} +0 -0
  300. /package/dist/{chunk-JSFU7AFZ.js.map → chunk-OANSZGAG.js.map} +0 -0
  301. /package/dist/{chunk-364M2T3D.js.map → chunk-OIIAIY7J.js.map} +0 -0
  302. /package/dist/{chunk-IP5QGSNH.js.map → chunk-OXX4ORRL.js.map} +0 -0
  303. /package/dist/{chunk-RTTPBBAN.js.map → chunk-OXYXMGRH.js.map} +0 -0
  304. /package/dist/{chunk-K7BN34IA.js.map → chunk-PL26BCRN.js.map} +0 -0
  305. /package/dist/{chunk-PM72NSEW.js.map → chunk-PYCTZ2HX.js.map} +0 -0
  306. /package/dist/{chunk-P3NXZAV7.js.map → chunk-PZEADMX4.js.map} +0 -0
  307. /package/dist/{chunk-QPH52AZB.js.map → chunk-QNDOTDJK.js.map} +0 -0
  308. /package/dist/{chunk-LZACDIM2.js.map → chunk-RBGRQEW3.js.map} +0 -0
  309. /package/dist/{chunk-V4JZCTMK.js.map → chunk-TKBW4YLS.js.map} +0 -0
  310. /package/dist/{chunk-4D2YH47Z.js.map → chunk-TRW7N4FS.js.map} +0 -0
  311. /package/dist/{chunk-OQJKY3Z5.js.map → chunk-UAAAXSKQ.js.map} +0 -0
  312. /package/dist/{chunk-4LJSCW24.js.map → chunk-UM73Q46J.js.map} +0 -0
  313. /package/dist/{chunk-7EJCAFMS.js.map → chunk-UTXYOWDR.js.map} +0 -0
  314. /package/dist/{chunk-OGOADX26.js.map → chunk-UUYAW3JH.js.map} +0 -0
  315. /package/dist/{chunk-UTGHW2Y5.js.map → chunk-UVUBLLM3.js.map} +0 -0
  316. /package/dist/{chunk-4GGTC2AX.js.map → chunk-V4FI2TG6.js.map} +0 -0
  317. /package/dist/{chunk-MO6N4QYY.js.map → chunk-VARNSLUW.js.map} +0 -0
  318. /package/dist/{chunk-RQUF3FXK.js.map → chunk-VEB7NEON.js.map} +0 -0
  319. /package/dist/{chunk-SSFO7B7H.js.map → chunk-VLBFSD7B.js.map} +0 -0
  320. /package/dist/{chunk-JNIVZNIB.js.map → chunk-WKVL2G7X.js.map} +0 -0
  321. /package/dist/{chunk-KJXP6RUG.js.map → chunk-WNEVEMJ3.js.map} +0 -0
  322. /package/dist/{chunk-I4MYLDRY.js.map → chunk-WQIYGYY6.js.map} +0 -0
  323. /package/dist/{chunk-GQJKVQJR.js.map → chunk-WYLBEJQI.js.map} +0 -0
  324. /package/dist/{chunk-JCPR47NY.js.map → chunk-XNROYP5E.js.map} +0 -0
  325. /package/dist/{chunk-XNAOCY67.js.map → chunk-XVZIBMSY.js.map} +0 -0
  326. /package/dist/{chunk-TZZFGR4E.js.map → chunk-YGVZ4QOZ.js.map} +0 -0
  327. /package/dist/{chunk-C5JXYJHN.js.map → chunk-YZTFQBYY.js.map} +0 -0
  328. /package/dist/{chunk-JTLXVXYH.js.map → chunk-ZCRDYQL6.js.map} +0 -0
  329. /package/dist/{chunk-CRVL6SLC.js.map → chunk-ZXTAHSQU.js.map} +0 -0
  330. /package/dist/{resolution-ZU5BIAJA.js.map → entity-canonical-id-migration-GD5MPFUF.js.map} +0 -0
@@ -114,6 +114,7 @@ export class OrchestratorInitCoordinator {
114
114
  private startupEpochAbort:
115
115
  | { epoch: number; controller: AbortController }
116
116
  | undefined;
117
+ private readonly startupStorageWrites = new Set<Promise<void>>();
117
118
 
118
119
  constructor(
119
120
  private readonly deps: OrchestratorInitDeps,
@@ -127,6 +128,25 @@ export class OrchestratorInitCoordinator {
127
128
  this.activeStartupEpoch = null;
128
129
  }
129
130
 
131
+ private async initializeStorage(storage: StorageManager): Promise<void> {
132
+ const write = storage.ensureDirectories({ waitForEntityMigration: false });
133
+ this.startupStorageWrites.add(write);
134
+ try {
135
+ await write;
136
+ } finally {
137
+ this.startupStorageWrites.delete(write);
138
+ }
139
+ }
140
+
141
+ async drainEntityMigrations(): Promise<void> {
142
+ await Promise.allSettled([...this.startupStorageWrites]);
143
+ const drains = [this.deps.storage.drainEntityCanonicalIdMigration()];
144
+ this.deps.storageRouter.forEachCachedStorage((storage) => {
145
+ drains.push(storage.drainEntityCanonicalIdMigration());
146
+ });
147
+ await Promise.all(drains);
148
+ }
149
+
130
150
  private beginStartupEpoch(): number {
131
151
  this.startupEpochAbort?.controller.abort();
132
152
  this.startupEpochAbort = undefined;
@@ -305,7 +325,8 @@ export class OrchestratorInitCoordinator {
305
325
  logger: (message) => log.info(message),
306
326
  });
307
327
  await this.deps.storage.loadAliases();
308
- await this.deps.storage.ensureDirectories();
328
+ if (exitIfStale()) return;
329
+ await this.initializeStorage(this.deps.storage);
309
330
  await Promise.all(
310
331
  this.deps.config.externalWikis.map((wiki) =>
311
332
  assertExternalWikiRootOutsideMemoryDirCanonical(
@@ -323,7 +344,8 @@ export class OrchestratorInitCoordinator {
323
344
  for (const ns of namespaces) {
324
345
  const sm = await this.deps.storageRouter.storageFor(ns);
325
346
  await sm.loadAliases().catch(() => undefined);
326
- await sm.ensureDirectories();
347
+ if (exitIfStale()) return;
348
+ await this.initializeStorage(sm);
327
349
  }
328
350
  // Explicitly seed the catalog with all configured namespaces at startup
329
351
  // (round 6, cursor Medium — NBLlR). The storageFor loop above fires the
@@ -564,7 +586,8 @@ export class OrchestratorInitCoordinator {
564
586
  .catch((err) => {
565
587
  log.error(`deferred initialization failed (non-fatal): ${err}`);
566
588
  })
567
- .finally(() => {
589
+ .finally(async () => {
590
+ await this.drainEntityMigrations();
568
591
  settleOwnDeferredReady();
569
592
  });
570
593
  } catch (err) {
@@ -969,6 +969,7 @@ export class Orchestrator {
969
969
  this._orchestratorInitCoordinator?.retireStartupEpoch();
970
970
  this.abortDeferredInit();
971
971
  this.extractionQueueCoordinator.stopAccepting();
972
+ await this._orchestratorInitCoordinator?.drainEntityMigrations();
972
973
  if (this.wearablesAutoSyncHandle) {
973
974
  await this.wearablesAutoSyncHandle.stop();
974
975
  this.wearablesAutoSyncHandle = null;
@@ -0,0 +1,55 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, readFile, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { assertPathInsideRoot } from "../utils/path-containment.js";
5
+
6
+ export async function readStoreFileBuffer(filePath: string, memoryDir: string): Promise<Buffer> {
7
+ const root = path.resolve(memoryDir);
8
+ const entities = path.join(root, "entities");
9
+ const target = path.resolve(filePath);
10
+ const relative = path.relative(entities, target);
11
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
12
+ return readFile(filePath);
13
+ }
14
+ const handle = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
15
+ try {
16
+ for (let parent = path.dirname(target); ; parent = path.dirname(parent)) {
17
+ const info = await lstat(parent);
18
+ if (info.isSymbolicLink() || !info.isDirectory()) {
19
+ throw new Error(`Refusing unsafe entity directory: ${parent}.`);
20
+ }
21
+ if (parent === root) break;
22
+ }
23
+ const [opened, entry, realRoot, realEntities, realTarget] = await Promise.all([
24
+ handle.stat(), lstat(target), realpath(root), realpath(entities), realpath(target),
25
+ ]);
26
+ if (!opened.isFile() || entry.isSymbolicLink() || !entry.isFile()
27
+ || opened.dev !== entry.dev || opened.ino !== entry.ino
28
+ || realEntities !== path.join(realRoot, "entities")) {
29
+ throw new Error(`Refusing unsafe entity file: ${filePath}.`);
30
+ }
31
+ assertPathInsideRoot(realEntities, realTarget, filePath);
32
+ return await handle.readFile();
33
+ } finally {
34
+ await handle.close();
35
+ }
36
+ }
37
+
38
+
39
+ /**
40
+ * Atomic-replacement-tolerant variant: entity writes ship as temp-file +
41
+ * rename, so an open/lstat identity mismatch during a concurrent write is an
42
+ * ordinary race, not tampering. Retry a bounded number of times with fresh
43
+ * containment checks before surfacing the mismatch as unsafe.
44
+ */
45
+ export async function readStoreFileBufferTolerant(filePath: string, memoryDir: string, attempts = 3): Promise<Buffer> {
46
+ for (let attempt = 0; ; attempt += 1) {
47
+ try {
48
+ return await readStoreFileBuffer(filePath, memoryDir);
49
+ } catch (err) {
50
+ const message = err instanceof Error ? err.message : String(err);
51
+ const identityRace = /unsafe entity (?:file|directory)/.test(message);
52
+ if (!identityRace || attempt >= attempts - 1) throw err;
53
+ }
54
+ }
55
+ }
@@ -61,6 +61,7 @@ import {
61
61
  } from "./cipher.js";
62
62
  import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
63
63
  import { isErrnoCode } from "../utils/errno.js";
64
+ import { readStoreFileBufferTolerant as readStoreFileBuffer } from "./entity-file-read.js";
64
65
 
65
66
  // ---------------------------------------------------------------------------
66
67
  // Error classes
@@ -291,7 +292,7 @@ export async function readMaybeEncryptedFileBuffer(
291
292
  memoryDir?: string,
292
293
  opts?: { aadFilePath?: string }
293
294
  ): Promise<Buffer> {
294
- const buf = await readFile(filePath);
295
+ const buf = memoryDir ? await readStoreFileBuffer(filePath, memoryDir) : await readFile(filePath);
295
296
  if (!isEncryptedFile(buf)) {
296
297
  // Plain file — legacy or unencrypted store.
297
298
  return buf;
@@ -10,6 +10,38 @@ export class EntityCanonicalIdMigrationRunner {
10
10
  private readonly readFingerprint: () => Promise<string> = async () => "static",
11
11
  ) {}
12
12
 
13
+ /**
14
+ * First half of deferred startup: when waiting, ensure the migration runs;
15
+ * when deferring, pre-read the entity entries the store serves instead and
16
+ * let the migration start later (finishBegin detaches it).
17
+ */
18
+ public async begin(wait: boolean, entitiesDir: string): Promise<void> {
19
+ if (wait) {
20
+ await this.ensure();
21
+ return;
22
+ }
23
+ const { readSafeEntityEntries } = await import("./entity-canonical-id-migration.js");
24
+ await readSafeEntityEntries(entitiesDir);
25
+ }
26
+
27
+ /**
28
+ * Second half: return the directory-initialized barrier when waiting, or
29
+ * detach it with a warning when the caller deferred the migration.
30
+ */
31
+ public finishBegin(wait: boolean): Promise<void> {
32
+ const barrier = this.markDirectoriesInitialized();
33
+ if (wait) return barrier;
34
+ // Thrown rejections are Error-or-primitive; the message extractor
35
+ // handles both without asserting a shape.
36
+ const describeDeferredFailure = (failure: Error | string): string =>
37
+ failure instanceof Error ? failure.message : failure;
38
+ void barrier.catch(describeDeferredFailureAndWarn);
39
+ function describeDeferredFailureAndWarn(failure: Error | string): void {
40
+ console.warn(`entity canonical-id migration deferred at startup failed: ${describeDeferredFailure(failure)}`);
41
+ }
42
+ return Promise.resolve();
43
+ }
44
+
13
45
  public async markDirectoriesInitialized(): Promise<void> {
14
46
  this.directoriesInitialized = true;
15
47
  await this.ensure();
@@ -32,6 +64,10 @@ export class EntityCanonicalIdMigrationRunner {
32
64
  return this.ensure();
33
65
  }
34
66
 
67
+ public async drain(): Promise<void> {
68
+ while (this.inFlight) await this.inFlight.catch(() => undefined);
69
+ }
70
+
35
71
  private async runOrReuse(): Promise<void> {
36
72
  let fingerprintAtStart = await this.readFingerprint();
37
73
  if (this.migrationComplete && fingerprintAtStart === this.completedFingerprint) return;
@@ -370,7 +370,7 @@ async function assertEntityContentUnchanged(
370
370
  }
371
371
  }
372
372
 
373
- async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
373
+ export async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
374
374
  const rootStat = await lstat(entitiesDir);
375
375
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
376
376
  throw new Error("Refusing entity canonical-id migration through a symlinked or non-directory entities root.");
@@ -1,3 +1,4 @@
1
+ import { statSync } from "node:fs";
1
2
  /**
2
3
  * Write-boundary surface of the entity canonical-id migration (issue #2213).
3
4
  *
@@ -60,6 +61,13 @@ function parseJournalMappings(raw: string): Readonly<Record<string, string>> {
60
61
  return cleaned;
61
62
  }
62
63
 
64
+ /** Whether the journal says its migration completed. Throws like the mapping parser. */
65
+ function parseJournalComplete(raw: string): boolean {
66
+ // SAFETY: JSON.parse output is unknown; the shape is validated on access.
67
+ const parsed = JSON.parse(raw) as { complete?: unknown };
68
+ return parsed !== null && parsed.complete === true;
69
+ }
70
+
63
71
  type JournalRead =
64
72
  | { kind: "ok"; raw: string; key: string }
65
73
  | { kind: "missing" }
@@ -183,13 +191,57 @@ export async function appendCanonicalizedTombstone(
183
191
  * identity, so the next lookup retries instead of caching an empty table
184
192
  * under a valid key.
185
193
  */
194
+
195
+ /** Journal lookup result: mapping table plus its completion flag. */
196
+ export type HistoricalEntityIdsEntry = {
197
+ mappings: Readonly<Record<string, string>>;
198
+ complete: boolean;
199
+ };
200
+
201
+
202
+
203
+ /**
204
+ * Memoized journal-completion view keyed on the journal file's stat identity.
205
+ * entityList consults completion once per listed entity; without this each
206
+ * consult re-opens and re-reads the journal (quadratic I/O on large corpora).
207
+ */
208
+ export function memoizedJournalCompletion(
209
+ cache: HistoricalEntityCanonicalIdCache,
210
+ stateDir: () => string,
211
+ ): () => boolean {
212
+ let memo: { key: string; complete: boolean } | undefined;
213
+ return (): boolean => {
214
+ let key = "missing";
215
+ try {
216
+ const journalPath = path.join(stateDir(), ENTITY_CANONICAL_ID_MIGRATION_FILE);
217
+ const j = statSync(journalPath);
218
+ key = `${j.dev}:${j.ino}:${j.mtimeMs}:${j.ctimeMs}:${j.size}`;
219
+ } catch {
220
+ key = "missing";
221
+ }
222
+ if (memo && memo.key === key) return memo.complete;
223
+ const entry = cache.getEntry(stateDir());
224
+ memo = { key, complete: entry.complete };
225
+ return memo.complete;
226
+ };
227
+ }
228
+
186
229
  export class HistoricalEntityCanonicalIdCache {
187
230
  private mappings: Readonly<Record<string, string>> = {};
231
+ private complete = false;
188
232
  private key: string | null = null;
189
233
  private stateDir: string | null = null;
190
234
 
191
235
  get(stateDir: string): Readonly<Record<string, string>> {
192
- const lastKnown = this.stateDir === stateDir ? this.mappings : {};
236
+ return this.getEntry(stateDir).mappings;
237
+ }
238
+
239
+
240
+ /** Table plus the journal's completion flag, cached under one identity. */
241
+ getEntry(stateDir: string): HistoricalEntityIdsEntry {
242
+ const lastKnownMappings = this.stateDir === stateDir ? this.mappings : {};
243
+ const lastKnownComplete = this.stateDir === stateDir ? this.complete : false;
244
+ const lastKnown: HistoricalEntityIdsEntry = { mappings: lastKnownMappings, complete: lastKnownComplete };
193
245
  const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
194
246
  if (result.kind === "refused") {
195
247
  // Symlinked/non-regular journal: never follow it, never adopt its
@@ -208,9 +260,11 @@ export class HistoricalEntityCanonicalIdCache {
208
260
  const key = result.kind === "missing" ? "missing" : result.key;
209
261
  if (key !== this.key || stateDir !== this.stateDir) {
210
262
  let table: Readonly<Record<string, string>> = {};
263
+ let complete = false;
211
264
  if (result.kind === "ok") {
212
265
  try {
213
266
  table = parseJournalMappings(result.raw);
267
+ complete = parseJournalComplete(result.raw);
214
268
  } catch {
215
269
  // Parse failed under a VALID identity: do not commit the key —
216
270
  // the next lookup retries instead of pinning an empty table.
@@ -218,10 +272,11 @@ export class HistoricalEntityCanonicalIdCache {
218
272
  }
219
273
  }
220
274
  this.mappings = table;
275
+ this.complete = complete;
221
276
  this.key = key;
222
277
  this.stateDir = stateDir;
223
278
  }
224
- return this.mappings;
279
+ return { mappings: this.mappings, complete: this.complete };
225
280
  }
226
281
  }
227
282
 
@@ -23,7 +23,7 @@ import {
23
23
  } from "../utils/macos-replication-junk.js";
24
24
  import { getCachedEntities, setCachedEntities } from "../memory-cache.js";
25
25
  import type { VersionTrigger } from "../page-versioning.js";
26
- import { SecureStoreLockedError } from "../secure-store/secure-fs.js";
26
+ import { SecureStoreDecryptError, SecureStoreLockedError } from "../secure-store/secure-fs.js";
27
27
  import type {
28
28
  EntityActivityEntry,
29
29
  EntityFile,
@@ -67,6 +67,8 @@ export interface EntityStoreDeps {
67
67
  readonly entitiesDir: string;
68
68
  readonly entitySchemas: PluginConfig["entitySchemas"] | undefined;
69
69
  currentHistoricalIds(): Readonly<Record<string, string>>;
70
+ /** True when the entity canonical-id journal says migration completed. */
71
+ entityCanonicalMigrationComplete(): boolean;
70
72
  findMatchingEntity(proposedName: string, type: string): Promise<string | null>;
71
73
  getEntityCacheSecureStoreKey(): string;
72
74
  getEntityMutationVersion(): number;
@@ -373,18 +375,56 @@ export class EntityStore {
373
375
  return normalized;
374
376
  }
375
377
 
376
- async readEntity(name: string): Promise<string> {
377
- const filePath = this.deps.resolveEntityFilePath(name);
378
- if (filePath === null) {
379
- return "";
380
- }
378
+ private async readEntityFileResolvingRename(firstPath: string, entityId: string) {
381
379
  try {
382
- return await this.deps.readStorageSecureFile(filePath);
380
+ return { id: entityId, content: await this.deps.readStorageSecureFile(firstPath) };
383
381
  } catch (err) {
384
382
  if (err instanceof SecureStoreLockedError) throw err;
383
+ // ENOENT: renamed away (retry below). ELOOP: planted symlink — skip.
384
+ if (isErrnoCode(err, "ELOOP")) return null;
385
385
  if (!isErrnoCode(err, "ENOENT")) throw err;
386
+ }
387
+ const historicalIds = this.deps.currentHistoricalIds();
388
+ const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
389
+ const candidates = new Set([canonical]);
390
+ for (const legacy of Object.keys(historicalIds)) {
391
+ if (resolveHistoricalEntityCanonicalId(legacy, historicalIds) === canonical) candidates.add(legacy);
392
+ }
393
+ candidates.delete(entityId);
394
+ for (const candidate of candidates) {
395
+ try {
396
+ return { id: candidate, content: await this.deps.readStorageSecureFile(
397
+ path.join(this.deps.entitiesDir, `${candidate}.md`),
398
+ ) };
399
+ } catch (err) {
400
+ if (err instanceof SecureStoreLockedError) throw err;
401
+ // ENOENT/ELOOP: gone or a planted symlink — try the next candidate.
402
+ if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) continue;
403
+ throw err;
404
+ }
405
+ }
406
+ return null;
407
+ }
408
+
409
+ async readEntity(name: string): Promise<string> {
410
+ const filePath = this.deps.resolveEntityFilePath(name);
411
+ if (filePath === null) {
386
412
  return "";
387
413
  }
414
+ // Batch reads retry renames unconditionally (a listed file that ENOENTs
415
+ // is definitionally a race), but a DIRECT lookup of a legacy id must stop
416
+ // resolving once the journal says migration completed and its file is gone.
417
+ if (this.deps.entityCanonicalMigrationComplete()) {
418
+ try {
419
+ return await this.deps.readStorageSecureFile(filePath);
420
+ } catch (err) {
421
+ if (err instanceof SecureStoreLockedError) throw err;
422
+ // ENOENT: gone. ELOOP: a planted symlink the open refused to follow.
423
+ if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) return "";
424
+ throw err;
425
+ }
426
+ }
427
+ return (await this.readEntityFileResolvingRename(filePath, name))?.content ?? "";
388
428
  }
389
429
 
390
430
  /**
@@ -434,6 +474,16 @@ export class EntityStore {
434
474
  * Symlinks, FIFOs, and devices are skipped before any read. The entities
435
475
  * root is refused separately, before the directory is listed.
436
476
  */
477
+ private async isMissingEntry(file: string): Promise<boolean> {
478
+ try {
479
+ await lstat(path.join(this.deps.entitiesDir, file));
480
+ return false;
481
+ } catch (err) {
482
+ if (isErrnoCode(err, "ENOENT")) return true;
483
+ throw err;
484
+ }
485
+ }
486
+
437
487
  private async isContainedRegularEntityFile(file: string): Promise<boolean> {
438
488
  const filePath = path.join(this.deps.entitiesDir, file);
439
489
  let entryStat;
@@ -491,23 +541,54 @@ export class EntityStore {
491
541
  const names: string[] = [];
492
542
  const batchSize = 100;
493
543
  for (let i = 0; i < mdFiles.length; i += batchSize) {
544
+ // Journal table snapshot once per call (per-entry reads are quadratic).
545
+ const historicalIds = this.deps.currentHistoricalIds();
546
+ const snapshotIds = new Set(mdFiles.map((file) => file.slice(0, -3)));
547
+ const journalComplete = this.deps.entityCanonicalMigrationComplete();
548
+ const resolveCanonical = (entityId: string): string => {
549
+ const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
550
+ if (canonical === entityId) return entityId;
551
+ // Completed journal: the on-disk filename is authoritative; a
552
+ // late legacy file stays under its own readable name.
553
+ if (journalComplete) return entityId;
554
+ // Both claimants coexisting (copy-before-unlink) keep both names.
555
+ if (snapshotIds.has(canonical)) return entityId;
556
+ return canonical;
557
+ };
494
558
  const resolved = await Promise.all(mdFiles.slice(i, i + batchSize).map(async (file) => {
559
+ const entityId = file.slice(0, -3);
495
560
  try {
496
- if (!(await this.isIndexableEntityMarkdown(file))) return null;
561
+ // Report the canonical id for every indexable file so legacy
562
+ // names surface as their canonical identity from the start.
563
+ if (await this.isIndexableEntityMarkdown(file)) {
564
+ return resolveCanonical(entityId);
565
+ }
497
566
  } catch (err) {
498
- if (err instanceof SecureStoreLockedError) throw err;
499
- return null;
567
+ // Lock and decrypt errors abort the listing; anything else
568
+ // (EACCES) fails only this entry — one unhealthy file must not
569
+ // empty it.
570
+ if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
571
+ }
572
+ // Vanished listed entry: resolve through the journal (rename
573
+ // race); lstat failures stay within this entry.
574
+ try {
575
+ if (await this.isMissingEntry(file)) {
576
+ const canonical = resolveHistoricalEntityCanonicalId(entityId, this.deps.currentHistoricalIds());
577
+ if (canonical !== entityId) return canonical;
578
+ }
579
+ } catch (err) {
580
+ if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
500
581
  }
501
- return file.slice(0, -3);
582
+ return null;
502
583
  }));
503
- for (const name of resolved) {
504
- if (name) names.push(name);
505
- }
584
+ names.push(...resolved.filter((name): name is string => name !== null));
506
585
  }
507
- names.sort();
508
- return names;
586
+ // The copy-before-unlink migration window can snapshot both the legacy
587
+ // and canonical names; the legacy fallback then resolves to the same
588
+ // canonical id. Deduplicate so the total stays correct.
589
+ return [...new Set(names)].sort();
509
590
  } catch (err) {
510
- if (err instanceof SecureStoreLockedError) throw err;
591
+ if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
511
592
  return [];
512
593
  }
513
594
  }
@@ -636,10 +717,22 @@ export class EntityStore {
636
717
  if (cached) return cached;
637
718
 
638
719
  try {
639
- const entries = await this.readEntityDirectoryNames();
720
+ // A symlinked/replaced entities root has no readable entries (merged
721
+ // #3207 semantics): resolve empty rather than following it. Within a
722
+ // trusted root, readdir directly so planted entries reach the secure
723
+ // read instead of being swallowed by the listing guards.
724
+ if (!(await this.isTrustedEntitiesRoot())) return [];
725
+ const listed = await readdir(this.deps.entitiesDir);
726
+ const { kept: entries, ignored } = withoutMacOsReplicationJunk(listed, (name) => name);
727
+ warnIgnoredMacOsReplicationJunk(this.deps.entitiesDir, ignored, (message) => {
728
+ log.warn(`entity load ${message}`);
729
+ });
640
730
  const mdFiles = entries.filter((e) => e.endsWith(".md"));
641
731
  if (mdFiles.length === 0) return [];
642
732
 
733
+ const historicalIds = this.deps.currentHistoricalIds();
734
+ const seen = new Map<string, Set<string>>();
735
+
643
736
  // Read all entity files in parallel batches to avoid O(N) sequential I/O.
644
737
  // With 3000+ entity files, sequential reads can take 15-20s under load.
645
738
  // Batching at 100 keeps file-descriptor pressure manageable while staying fast.
@@ -652,19 +745,39 @@ export class EntityStore {
652
745
  const batch = mdFiles.slice(i, i + BATCH_SIZE);
653
746
  const results = await Promise.all(
654
747
  batch.map(async (entry) => {
748
+ const entityId = entry.slice(0, -".md".length);
749
+ const entryPath = path.join(this.deps.entitiesDir, entry);
655
750
  try {
656
- if (!(await this.isContainedRegularEntityFile(entry))) return null;
657
- return await this.deps.readStorageSecureFile(path.join(this.deps.entitiesDir, entry));
751
+ if (!(await this.isContainedRegularEntityFile(entry))) {
752
+ // A listed entry that vanished races a background migration
753
+ // rename: resolve through historical ids like a direct read.
754
+ // Still-present symlinks/dirs stay skipped.
755
+ if (await this.isMissingEntry(entry)) {
756
+ return await this.readEntityFileResolvingRename(entryPath, entityId);
757
+ }
758
+ return null;
759
+ }
760
+ return await this.readEntityFileResolvingRename(entryPath, entityId);
658
761
  } catch (err) {
659
- if (err instanceof SecureStoreLockedError) throw err;
660
- if (!isErrnoCode(err, "ENOENT")) throw err;
762
+ // Lock and decrypt failures abort the corpus read (the store
763
+ // is unusable or tampered); anything else isolates to this entry.
764
+ if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
765
+ log.warn(`entity read isolated failure for ${entry}: ${err instanceof Error ? err.message : String(err)}`);
661
766
  return null;
662
767
  }
663
768
  }),
664
769
  );
665
- for (const content of results) {
666
- if (content === null) continue;
667
- const entity = parseEntityFile(content, this.deps.entitySchemas);
770
+ for (const result of results) {
771
+ if (result === null) continue;
772
+ const canonical = resolveHistoricalEntityCanonicalId(result.id, historicalIds);
773
+ let contents = seen.get(canonical);
774
+ if (contents?.has(result.content)) continue;
775
+ if (!contents) {
776
+ contents = new Set();
777
+ seen.set(canonical, contents);
778
+ }
779
+ contents.add(result.content);
780
+ const entity = parseEntityFile(result.content, this.deps.entitySchemas);
668
781
  if (!entity.name.trim() || !entity.type.trim()) continue;
669
782
  entities.push(entity);
670
783
  }
package/src/storage.ts CHANGED
@@ -2323,10 +2323,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
2323
2323
  private _entityStore: EntityStore | undefined;
2324
2324
 
2325
2325
  private get entityStore(): EntityStore {
2326
- if (!this._entityStore) {
2327
- this._entityStore = new EntityStore(selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this));
2328
- }
2329
- return this._entityStore;
2326
+ return (this._entityStore ??= new EntityStore(selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this)));
2330
2327
  }
2331
2328
 
2332
2329
  /** IdentityContinuityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
@@ -3537,6 +3534,16 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3537
3534
  private currentHistoricalIds(): Readonly<Record<string, string>> {
3538
3535
  return this.historicalEntityCanonicalIds.get(this.stateDir);
3539
3536
  }
3537
+
3538
+ /** Journal completion for EntityStore's legacy-id retry gate (live view). */
3539
+ entityCanonicalMigrationComplete = entityRefs.memoizedJournalCompletion(this.historicalEntityCanonicalIds, () => this.stateDir);
3540
+
3541
+ normalizeEntityName(raw: string, type: string): string {
3542
+ return entityRefs.resolveHistoricalEntityCanonicalId(
3543
+ this.entityStore.normalizeEntityName(raw, type),
3544
+ this.currentHistoricalIds()
3545
+ );
3546
+ }
3540
3547
  /** Post-persist repair subsystem (issue #2213) — storage/entity-ref-repair.ts. */
3541
3548
  private _entityRefRepair: EntityRefRepair | undefined;
3542
3549
  private get entityRefRepair(): EntityRefRepair {
@@ -3561,12 +3568,6 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3561
3568
  () => this.runLegacyEntityCanonicalIdMigration(),
3562
3569
  () => entityMigration.getFingerprint(this.baseDir, this.entitiesDir, () => String(this.getEntityMutationVersion()))
3563
3570
  );
3564
- normalizeEntityName(raw: string, type: string): string {
3565
- return entityRefs.resolveHistoricalEntityCanonicalId(
3566
- this.entityStore.normalizeEntityName(raw, type),
3567
- this.currentHistoricalIds()
3568
- );
3569
- }
3570
3571
 
3571
3572
  /**
3572
3573
  * Read-only view of this store's user alias table, for call sites that
@@ -3623,7 +3624,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3623
3624
  return completionFingerprint;
3624
3625
  }
3625
3626
 
3626
- async ensureDirectories(): Promise<void> {
3627
+ async ensureDirectories(options: { waitForEntityMigration?: boolean } = {}): Promise<void> {
3627
3628
  await mkdir(this.baseDir, { recursive: true });
3628
3629
  await entityMigration.validateRoots(this.baseDir, this.entitiesDir, this.stateDir, this.archiveDir);
3629
3630
  const today = new Date().toISOString().slice(0, 10);
@@ -3640,7 +3641,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3640
3641
  await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
3641
3642
  await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
3642
3643
  await mkdir(path.join(this.baseDir, "config"), { recursive: true });
3643
- await this.entityCanonicalIdMigration.ensure();
3644
+ await this.entityCanonicalIdMigration.begin(options.waitForEntityMigration !== false, this.entitiesDir);
3644
3645
  // Activate the corpus sentinel at setup (issue #1902, Cursor Medium): an
3645
3646
  // existing corpus predating this feature has a size-0/absent sentinel, which
3646
3647
  // getCachedMemories treats as a miss — so a read-heavy workload would never
@@ -3654,9 +3655,9 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3654
3655
  if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
3655
3656
  this.bumpMemoryCorpusVersion();
3656
3657
  }
3657
- await this.entityCanonicalIdMigration.markDirectoriesInitialized();
3658
+ return this.entityCanonicalIdMigration.finishBegin(options.waitForEntityMigration !== false);
3658
3659
  }
3659
-
3660
+ drainEntityCanonicalIdMigration = (): Promise<void> => this.entityCanonicalIdMigration.drain();
3660
3661
  /**
3661
3662
  * Resolve the on-disk write path for a memory of the given category, creating
3662
3663
  * the target directory. Category routing goes through the shared
@@ -4079,7 +4080,6 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
4079
4080
  await this.addActiveFactContentHash(memory);
4080
4081
  }
4081
4082
 
4082
-
4083
4083
  private async syncFactHashIndexAfterRewrite(before: MemoryFile, after: MemoryFile): Promise<void> {
4084
4084
  if (before.frontmatter.category !== "fact" && after.frontmatter.category !== "fact") return;
4085
4085