@remnic/core 10.0.27 → 10.0.29

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 (372) hide show
  1. package/dist/access-admin-ops-surface.js +54 -53
  2. package/dist/access-authorization-probe.js +55 -54
  3. package/dist/access-boundary.js +54 -53
  4. package/dist/access-citation-namespace.js +3 -2
  5. package/dist/access-cli.js +106 -105
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +54 -53
  8. package/dist/access-http-briefing.js +55 -54
  9. package/dist/access-http-query.js +56 -55
  10. package/dist/access-http.js +73 -72
  11. package/dist/access-identity-continuity-surface.js +54 -53
  12. package/dist/access-lcm-surface.js +54 -53
  13. package/dist/access-mcp.js +65 -64
  14. package/dist/access-namespace-preflight.js +54 -53
  15. package/dist/access-observe-write-category.js +11 -11
  16. package/dist/access-observe-write-surface.js +54 -53
  17. package/dist/access-offline-manifest.js +10 -10
  18. package/dist/access-operations-batch.js +56 -55
  19. package/dist/access-operations.js +63 -62
  20. package/dist/access-recall-concurrency.js +54 -53
  21. package/dist/access-recall-diagnostics.js +2 -2
  22. package/dist/access-recall-response.js +54 -53
  23. package/dist/access-recall-surface.js +54 -53
  24. package/dist/access-schema.js +6 -5
  25. package/dist/access-service.js +54 -53
  26. package/dist/access-surface-catalog.js +56 -55
  27. package/dist/access-surface-catalog.js.map +1 -1
  28. package/dist/active-recall.js +24 -23
  29. package/dist/active-recall.js.map +1 -1
  30. package/dist/{auto-sync-UQESPLCB.js → auto-sync-MX2AHVPQ.js} +8 -8
  31. package/dist/briefing.js +20 -19
  32. package/dist/buffer.js +8 -8
  33. package/dist/calibration.js +2 -2
  34. package/dist/{capsule-merge-LGYURS3C.js → capsule-merge-LGYZAMO4.js} +3 -3
  35. package/dist/causal-consolidation.js +22 -21
  36. package/dist/causal-consolidation.js.map +1 -1
  37. package/dist/{chunk-G6XEYFCO.js → chunk-27OJ5G3D.js} +14 -14
  38. package/dist/{chunk-5LRV557D.js → chunk-2FIJQNL7.js} +2 -2
  39. package/dist/{chunk-ETPIYK4J.js → chunk-2VBDSZ7U.js} +2 -2
  40. package/dist/{chunk-QNKEMFZY.js → chunk-2WDMUQOH.js} +2 -2
  41. package/dist/{chunk-W6MOLW5S.js → chunk-2XVLY424.js} +4 -4
  42. package/dist/{chunk-P55FACBG.js → chunk-3BOQPLCF.js} +6 -6
  43. package/dist/{chunk-27SAUQ5R.js → chunk-3CHHQXAR.js} +2 -2
  44. package/dist/{chunk-3GYSZHMT.js → chunk-3CZRBTJK.js} +2 -2
  45. package/dist/{chunk-FJJWA7NL.js → chunk-3EJQXF3B.js} +2 -2
  46. package/dist/{chunk-HFSPY26A.js → chunk-3IXNL6SK.js} +2 -2
  47. package/dist/{chunk-PLD6SOU7.js → chunk-3SN2D43X.js} +2 -2
  48. package/dist/{chunk-COGLFRXT.js → chunk-4HNINU7Q.js} +31 -31
  49. package/dist/{chunk-CCDHRDQ3.js → chunk-4HYP4OKH.js} +2 -2
  50. package/dist/{chunk-GKJJNO7C.js → chunk-4OE4UE2Y.js} +6 -6
  51. package/dist/{chunk-7RTNJ6M2.js → chunk-4QPW6JVI.js} +2 -2
  52. package/dist/{chunk-5SMR542T.js → chunk-5GT4ESLR.js} +2 -2
  53. package/dist/{chunk-DWL6YIZH.js → chunk-5JI3XWI2.js} +2 -2
  54. package/dist/{chunk-27LPB4NS.js → chunk-5NPZFXQH.js} +2 -2
  55. package/dist/{chunk-Y6IF6H2U.js → chunk-5SMHQW45.js} +2 -2
  56. package/dist/{chunk-VN3GHPBU.js → chunk-654GT5UP.js} +2 -2
  57. package/dist/{chunk-3E6DGSGZ.js → chunk-6MC7UQ6D.js} +2 -2
  58. package/dist/{chunk-655FVTCD.js → chunk-74IN6PV5.js} +2 -2
  59. package/dist/{chunk-WFUO7NWX.js → chunk-7FZ4YVOH.js} +2 -2
  60. package/dist/{chunk-MWWAQXZ6.js → chunk-7HFTCBCZ.js} +2 -2
  61. package/dist/{chunk-7IOB5F7Y.js → chunk-7VB3Y5OL.js} +3 -3
  62. package/dist/{chunk-YEF5AAWN.js → chunk-A54E2ZTD.js} +2 -2
  63. package/dist/{chunk-DEY7B77Y.js → chunk-AHCELJMH.js} +2 -2
  64. package/dist/{chunk-PIWFNPQT.js → chunk-AL3TCNSJ.js} +4 -4
  65. package/dist/{chunk-EET24NSB.js → chunk-AUYFUBBE.js} +6 -6
  66. package/dist/{chunk-L3IP56F4.js → chunk-BIE3ZH3N.js} +33 -33
  67. package/dist/{chunk-A3IURDU2.js → chunk-C2AOL6MK.js} +3 -3
  68. package/dist/{chunk-BDQMMX3M.js → chunk-C2QVDLQV.js} +2 -2
  69. package/dist/{chunk-H75L4ZOH.js → chunk-CKR7LXEK.js} +71 -49
  70. package/dist/chunk-CKR7LXEK.js.map +1 -0
  71. package/dist/{chunk-AWLQNLTM.js → chunk-CSGFGQBI.js} +2 -2
  72. package/dist/{chunk-JHQAXATW.js → chunk-D4RV54HH.js} +2 -2
  73. package/dist/{chunk-33UAWCBN.js → chunk-DDLRKPR2.js} +6 -6
  74. package/dist/{chunk-3X3PNU3T.js → chunk-DLH3ECZP.js} +5 -5
  75. package/dist/chunk-DNIG3JME.js +892 -0
  76. package/dist/chunk-DNIG3JME.js.map +1 -0
  77. package/dist/{chunk-SA6GMZJY.js → chunk-DP3TSAYT.js} +2 -2
  78. package/dist/{chunk-H2X22KZW.js → chunk-FN6OFDBZ.js} +2 -2
  79. package/dist/{chunk-SU5V2LWR.js → chunk-G6DAE2LT.js} +2 -2
  80. package/dist/{chunk-V7KGDLBC.js → chunk-GQ7VWY6N.js} +2 -2
  81. package/dist/{chunk-SYUNCM26.js → chunk-GSKW64NB.js} +2 -2
  82. package/dist/{chunk-4WT6I4VG.js → chunk-GWB7ZD2A.js} +2 -2
  83. package/dist/{chunk-JYZLDZ2R.js → chunk-HG2VVIKI.js} +9 -9
  84. package/dist/{chunk-3VRMUNWL.js → chunk-HJ444KNS.js} +15 -7
  85. package/dist/chunk-HJ444KNS.js.map +1 -0
  86. package/dist/{chunk-23ZQPP2G.js → chunk-HOSJESKO.js} +3 -3
  87. package/dist/{chunk-EU6IRUCY.js → chunk-IDPZCHET.js} +2 -2
  88. package/dist/{chunk-VMHCC7LO.js → chunk-IO2UQVR2.js} +2 -2
  89. package/dist/{chunk-BWXKJDDH.js → chunk-ISMZ5UNE.js} +3 -3
  90. package/dist/{chunk-SX4QDWXK.js → chunk-IWUFKS4J.js} +2 -2
  91. package/dist/{chunk-I27WOMJ7.js → chunk-IY7DGT2Z.js} +2 -2
  92. package/dist/{chunk-STBX5X6B.js → chunk-JAYNUYSJ.js} +2 -2
  93. package/dist/{chunk-N34ZW6E4.js → chunk-K2REHR42.js} +2 -2
  94. package/dist/{chunk-UJPGQCXD.js → chunk-KFKFK6G6.js} +10 -10
  95. package/dist/{chunk-2HLMFRZ7.js → chunk-LDEKVBVO.js} +4 -4
  96. package/dist/{chunk-42KSEX6C.js → chunk-LDXNKEYB.js} +1 -1
  97. package/dist/chunk-LDXNKEYB.js.map +1 -0
  98. package/dist/{chunk-MPZYCRC6.js → chunk-M236YHUH.js} +5 -5
  99. package/dist/{chunk-L5VCT7LU.js → chunk-MCLZM5G7.js} +2 -2
  100. package/dist/{chunk-7Z3DLNSD.js → chunk-MGKH5KO3.js} +2 -2
  101. package/dist/{chunk-PUDYANAX.js → chunk-MMBLOJJB.js} +2 -2
  102. package/dist/{chunk-EFHNFTWB.js → chunk-MND4YJSX.js} +2 -2
  103. package/dist/{chunk-HJQXHLQQ.js → chunk-MROA6OFU.js} +2 -2
  104. package/dist/{chunk-BQOGQULP.js → chunk-MZZEMPCI.js} +2 -2
  105. package/dist/{chunk-ERAVI25Z.js → chunk-NAPHQ2MG.js} +2 -2
  106. package/dist/{chunk-L5TJWQEF.js → chunk-NB2YX6X2.js} +6 -6
  107. package/dist/{chunk-IHGLQJW6.js → chunk-NCPR2FOJ.js} +3 -3
  108. package/dist/{chunk-UKWRCQWT.js → chunk-NOMHXZD7.js} +2 -2
  109. package/dist/{chunk-DF2G4IXN.js → chunk-NPO4ZQFY.js} +3 -3
  110. package/dist/{chunk-LWFWZTWX.js → chunk-NQV6G6MU.js} +2 -2
  111. package/dist/{chunk-5HT35H4L.js → chunk-NRLWHZVS.js} +2 -2
  112. package/dist/{chunk-GCZHKBY2.js → chunk-OANSZGAG.js} +2 -2
  113. package/dist/{chunk-BHQPOCQE.js → chunk-OIIAIY7J.js} +3 -3
  114. package/dist/{chunk-FFGHFU7A.js → chunk-OXX4ORRL.js} +17 -17
  115. package/dist/{chunk-RTTPBBAN.js → chunk-OXYXMGRH.js} +4 -4
  116. package/dist/{chunk-K7BN34IA.js → chunk-PL26BCRN.js} +5 -5
  117. package/dist/{chunk-PM72NSEW.js → chunk-PYCTZ2HX.js} +2 -2
  118. package/dist/{chunk-AQ52ZFBC.js → chunk-PZEADMX4.js} +2 -2
  119. package/dist/{chunk-QPH52AZB.js → chunk-QNDOTDJK.js} +2 -2
  120. package/dist/{chunk-LZACDIM2.js → chunk-RBGRQEW3.js} +4 -4
  121. package/dist/{chunk-H3AW6QZF.js → chunk-RRJ462HP.js} +2 -2
  122. package/dist/{chunk-V4JZCTMK.js → chunk-TKBW4YLS.js} +2 -2
  123. package/dist/{chunk-4D2YH47Z.js → chunk-TRW7N4FS.js} +2 -2
  124. package/dist/{chunk-IHS2UGYM.js → chunk-TTIEXXL3.js} +366 -1109
  125. package/dist/chunk-TTIEXXL3.js.map +1 -0
  126. package/dist/{chunk-OQJKY3Z5.js → chunk-UAAAXSKQ.js} +2 -2
  127. package/dist/{chunk-S36KJNQH.js → chunk-UM73Q46J.js} +2 -2
  128. package/dist/{chunk-7EJCAFMS.js → chunk-UTXYOWDR.js} +2 -2
  129. package/dist/{chunk-AYHPSYZI.js → chunk-UUYAW3JH.js} +2 -2
  130. package/dist/{chunk-MSMGQDVW.js → chunk-UVUBLLM3.js} +3 -3
  131. package/dist/{chunk-JZIZBJCW.js → chunk-V4FI2TG6.js} +3 -3
  132. package/dist/{chunk-MO6N4QYY.js → chunk-VARNSLUW.js} +4 -4
  133. package/dist/{chunk-WJAGO5LL.js → chunk-VEB7NEON.js} +4 -4
  134. package/dist/{chunk-GMZNJZRP.js → chunk-VLBFSD7B.js} +2 -2
  135. package/dist/{chunk-NZFFGO5O.js → chunk-VXWOJPSY.js} +36 -3
  136. package/dist/chunk-VXWOJPSY.js.map +1 -0
  137. package/dist/{chunk-JNIVZNIB.js → chunk-WKVL2G7X.js} +4 -4
  138. package/dist/{chunk-KJXP6RUG.js → chunk-WNEVEMJ3.js} +4 -4
  139. package/dist/{chunk-HNEOLXRF.js → chunk-WQIYGYY6.js} +2 -2
  140. package/dist/{chunk-GQJKVQJR.js → chunk-WYLBEJQI.js} +2 -2
  141. package/dist/{chunk-36DZ242S.js → chunk-XNROYP5E.js} +2 -2
  142. package/dist/{chunk-XNAOCY67.js → chunk-XVZIBMSY.js} +3 -3
  143. package/dist/{chunk-PXTLPC3P.js → chunk-YGVZ4QOZ.js} +4 -4
  144. package/dist/{chunk-JX32ROLZ.js → chunk-YZTFQBYY.js} +4 -4
  145. package/dist/{chunk-XSXM5LYZ.js → chunk-ZCRDYQL6.js} +2 -2
  146. package/dist/{chunk-MAAL6OZV.js → chunk-ZG3HZZ6K.js} +90 -36
  147. package/dist/chunk-ZG3HZZ6K.js.map +1 -0
  148. package/dist/{chunk-Z3XVYXQ3.js → chunk-ZXTAHSQU.js} +2 -2
  149. package/dist/cli.js +89 -88
  150. package/dist/coding/export-okf-codegraph.js +21 -20
  151. package/dist/coding/export-okf-codegraph.js.map +1 -1
  152. package/dist/compounding/engine.js +23 -22
  153. package/dist/config.js +24 -23
  154. package/dist/connectors/codex-materialize-runner.js +21 -20
  155. package/dist/connectors/codex-materialize.js +2 -2
  156. package/dist/connectors/index.js +21 -20
  157. package/dist/consolidation-provenance-check.js +3 -3
  158. package/dist/consolidation-undo.js +2 -2
  159. package/dist/contradiction/index.js +6 -5
  160. package/dist/convergence-refresh.js +22 -21
  161. package/dist/corpus-watermark.js +22 -21
  162. package/dist/deep-recall-http-glue.js +7 -6
  163. package/dist/entity-canonical-id-migration-GD5MPFUF.js +28 -0
  164. package/dist/entity-retrieval.js +20 -19
  165. package/dist/explicit-capture.js +8 -7
  166. package/dist/external-wiki-access.js +55 -54
  167. package/dist/extraction.js +27 -26
  168. package/dist/harmonic-retrieval.js +21 -20
  169. package/dist/harmonic-source-lookup.js +3 -2
  170. package/dist/index.js +106 -105
  171. package/dist/local-llm-rate-limit.d.ts +3 -1
  172. package/dist/local-llm-rate-limit.d.ts.map +1 -1
  173. package/dist/local-llm-rate-limit.js +1 -1
  174. package/dist/local-llm.js +2 -2
  175. package/dist/location/index.js +23 -22
  176. package/dist/maintenance/memory-governance.js +20 -19
  177. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -19
  178. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -21
  179. package/dist/maintenance/rebuild-memory-projection.js +21 -20
  180. package/dist/memory-browse-operations.js +55 -54
  181. package/dist/memory-subject.js +4 -3
  182. package/dist/namespaces/migrate.js +21 -20
  183. package/dist/namespaces/search.js +7 -7
  184. package/dist/namespaces/storage.js +20 -19
  185. package/dist/offline-sync-file-io.js +3 -2
  186. package/dist/offline-sync-impression-drain.js +4 -4
  187. package/dist/offline-sync-snapshot-stream.js +6 -5
  188. package/dist/offline-sync-upload-staging.js +4 -3
  189. package/dist/offline-sync.js +5 -4
  190. package/dist/operator-doctor-corpus.js +23 -22
  191. package/dist/operator-toolkit.js +34 -33
  192. package/dist/orchestration/maintenance.js +35 -34
  193. package/dist/orchestration/orchestrator-init.d.ts +3 -0
  194. package/dist/orchestration/orchestrator-init.d.ts.map +1 -1
  195. package/dist/orchestrator.d.ts.map +1 -1
  196. package/dist/orchestrator.js +106 -105
  197. package/dist/page-versioning.js +2 -2
  198. package/dist/recall-explain-renderer.js +3 -3
  199. package/dist/recall-navigate-access.js +2 -2
  200. package/dist/recall-navigate-disclosure.js +2 -2
  201. package/dist/recall-navigate.js +2 -2
  202. package/dist/recall-navigation-operations.js +55 -54
  203. package/dist/recall-navigation.js +54 -53
  204. package/dist/recall-state.js +2 -2
  205. package/dist/recall-xray-cli.js +4 -4
  206. package/dist/recall-xray-renderer.js +3 -3
  207. package/dist/recall-xray.js +2 -2
  208. package/dist/reconcile/manifest.js +4 -3
  209. package/dist/{resolution-ZU5BIAJA.js → resolution-AMYTPT5X.js} +7 -6
  210. package/dist/resolution-AMYTPT5X.js.map +1 -0
  211. package/dist/resume-bundles.js +25 -24
  212. package/dist/retrieval-agents.js +3 -2
  213. package/dist/salvage-envelope.js +6 -5
  214. package/dist/search/document-scanner.js +2 -2
  215. package/dist/search/factory.js +6 -6
  216. package/dist/search/index.js +6 -6
  217. package/dist/search/lancedb-backend.js +3 -3
  218. package/dist/search/meilisearch-backend.js +3 -3
  219. package/dist/search/orama-backend.js +3 -3
  220. package/dist/secure-store/entity-file-read.d.ts +9 -0
  221. package/dist/secure-store/entity-file-read.d.ts.map +1 -0
  222. package/dist/secure-store/index.js +3 -2
  223. package/dist/secure-store/secure-fs.d.ts.map +1 -1
  224. package/dist/semantic-consolidation.js +22 -21
  225. package/dist/semantic-rule-promotion.js +20 -19
  226. package/dist/semantic-rule-verifier.js +20 -19
  227. package/dist/shared-context/manager.js +2 -2
  228. package/dist/source-agent-qualifier.js +24 -23
  229. package/dist/standup/build.js +20 -19
  230. package/dist/storage/entity-canonical-id-migration-runner.d.ts +12 -0
  231. package/dist/storage/entity-canonical-id-migration-runner.d.ts.map +1 -1
  232. package/dist/storage/entity-canonical-id-migration.d.ts +1 -0
  233. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  234. package/dist/storage/entity-canonical-id-references.d.ts +14 -0
  235. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  236. package/dist/storage/entity-store.d.ts +4 -0
  237. package/dist/storage/entity-store.d.ts.map +1 -1
  238. package/dist/storage.d.ts +7 -2
  239. package/dist/storage.d.ts.map +1 -1
  240. package/dist/storage.js +19 -18
  241. package/dist/subject-config.js +5 -4
  242. package/dist/summarizer.js +3 -3
  243. package/dist/support-passport/index.js +57 -56
  244. package/dist/transfer/capsule-import.js +3 -3
  245. package/dist/transfer/export-okf.js +21 -20
  246. package/dist/types.d.ts +1 -1
  247. package/dist/types.d.ts.map +1 -1
  248. package/dist/types.js +1 -1
  249. package/dist/verified-recall.js +20 -19
  250. package/dist/who-knows-cli.js +21 -20
  251. package/dist/who-knows.js +20 -19
  252. package/dist/write-envelope.js +5 -4
  253. package/package.json +2 -2
  254. package/src/local-llm-rate-limit.test.ts +59 -1
  255. package/src/local-llm-rate-limit.ts +22 -7
  256. package/src/orchestration/orchestrator-init.ts +26 -3
  257. package/src/orchestrator.ts +1 -0
  258. package/src/secure-store/entity-file-read.ts +55 -0
  259. package/src/secure-store/secure-fs.ts +2 -1
  260. package/src/storage/entity-canonical-id-migration-runner.ts +36 -0
  261. package/src/storage/entity-canonical-id-migration.ts +1 -1
  262. package/src/storage/entity-canonical-id-references.ts +57 -2
  263. package/src/storage/entity-store.ts +138 -25
  264. package/src/storage.ts +15 -15
  265. package/src/types.ts +1 -1
  266. package/dist/chunk-3VRMUNWL.js.map +0 -1
  267. package/dist/chunk-42KSEX6C.js.map +0 -1
  268. package/dist/chunk-H75L4ZOH.js.map +0 -1
  269. package/dist/chunk-IHS2UGYM.js.map +0 -1
  270. package/dist/chunk-MAAL6OZV.js.map +0 -1
  271. package/dist/chunk-NZFFGO5O.js.map +0 -1
  272. /package/dist/{auto-sync-UQESPLCB.js.map → auto-sync-MX2AHVPQ.js.map} +0 -0
  273. /package/dist/{capsule-merge-LGYURS3C.js.map → capsule-merge-LGYZAMO4.js.map} +0 -0
  274. /package/dist/{chunk-G6XEYFCO.js.map → chunk-27OJ5G3D.js.map} +0 -0
  275. /package/dist/{chunk-5LRV557D.js.map → chunk-2FIJQNL7.js.map} +0 -0
  276. /package/dist/{chunk-ETPIYK4J.js.map → chunk-2VBDSZ7U.js.map} +0 -0
  277. /package/dist/{chunk-QNKEMFZY.js.map → chunk-2WDMUQOH.js.map} +0 -0
  278. /package/dist/{chunk-W6MOLW5S.js.map → chunk-2XVLY424.js.map} +0 -0
  279. /package/dist/{chunk-P55FACBG.js.map → chunk-3BOQPLCF.js.map} +0 -0
  280. /package/dist/{chunk-27SAUQ5R.js.map → chunk-3CHHQXAR.js.map} +0 -0
  281. /package/dist/{chunk-3GYSZHMT.js.map → chunk-3CZRBTJK.js.map} +0 -0
  282. /package/dist/{chunk-FJJWA7NL.js.map → chunk-3EJQXF3B.js.map} +0 -0
  283. /package/dist/{chunk-HFSPY26A.js.map → chunk-3IXNL6SK.js.map} +0 -0
  284. /package/dist/{chunk-PLD6SOU7.js.map → chunk-3SN2D43X.js.map} +0 -0
  285. /package/dist/{chunk-COGLFRXT.js.map → chunk-4HNINU7Q.js.map} +0 -0
  286. /package/dist/{chunk-CCDHRDQ3.js.map → chunk-4HYP4OKH.js.map} +0 -0
  287. /package/dist/{chunk-GKJJNO7C.js.map → chunk-4OE4UE2Y.js.map} +0 -0
  288. /package/dist/{chunk-7RTNJ6M2.js.map → chunk-4QPW6JVI.js.map} +0 -0
  289. /package/dist/{chunk-5SMR542T.js.map → chunk-5GT4ESLR.js.map} +0 -0
  290. /package/dist/{chunk-DWL6YIZH.js.map → chunk-5JI3XWI2.js.map} +0 -0
  291. /package/dist/{chunk-27LPB4NS.js.map → chunk-5NPZFXQH.js.map} +0 -0
  292. /package/dist/{chunk-Y6IF6H2U.js.map → chunk-5SMHQW45.js.map} +0 -0
  293. /package/dist/{chunk-VN3GHPBU.js.map → chunk-654GT5UP.js.map} +0 -0
  294. /package/dist/{chunk-3E6DGSGZ.js.map → chunk-6MC7UQ6D.js.map} +0 -0
  295. /package/dist/{chunk-655FVTCD.js.map → chunk-74IN6PV5.js.map} +0 -0
  296. /package/dist/{chunk-WFUO7NWX.js.map → chunk-7FZ4YVOH.js.map} +0 -0
  297. /package/dist/{chunk-MWWAQXZ6.js.map → chunk-7HFTCBCZ.js.map} +0 -0
  298. /package/dist/{chunk-7IOB5F7Y.js.map → chunk-7VB3Y5OL.js.map} +0 -0
  299. /package/dist/{chunk-YEF5AAWN.js.map → chunk-A54E2ZTD.js.map} +0 -0
  300. /package/dist/{chunk-DEY7B77Y.js.map → chunk-AHCELJMH.js.map} +0 -0
  301. /package/dist/{chunk-PIWFNPQT.js.map → chunk-AL3TCNSJ.js.map} +0 -0
  302. /package/dist/{chunk-EET24NSB.js.map → chunk-AUYFUBBE.js.map} +0 -0
  303. /package/dist/{chunk-L3IP56F4.js.map → chunk-BIE3ZH3N.js.map} +0 -0
  304. /package/dist/{chunk-A3IURDU2.js.map → chunk-C2AOL6MK.js.map} +0 -0
  305. /package/dist/{chunk-BDQMMX3M.js.map → chunk-C2QVDLQV.js.map} +0 -0
  306. /package/dist/{chunk-AWLQNLTM.js.map → chunk-CSGFGQBI.js.map} +0 -0
  307. /package/dist/{chunk-JHQAXATW.js.map → chunk-D4RV54HH.js.map} +0 -0
  308. /package/dist/{chunk-33UAWCBN.js.map → chunk-DDLRKPR2.js.map} +0 -0
  309. /package/dist/{chunk-3X3PNU3T.js.map → chunk-DLH3ECZP.js.map} +0 -0
  310. /package/dist/{chunk-SA6GMZJY.js.map → chunk-DP3TSAYT.js.map} +0 -0
  311. /package/dist/{chunk-H2X22KZW.js.map → chunk-FN6OFDBZ.js.map} +0 -0
  312. /package/dist/{chunk-SU5V2LWR.js.map → chunk-G6DAE2LT.js.map} +0 -0
  313. /package/dist/{chunk-V7KGDLBC.js.map → chunk-GQ7VWY6N.js.map} +0 -0
  314. /package/dist/{chunk-SYUNCM26.js.map → chunk-GSKW64NB.js.map} +0 -0
  315. /package/dist/{chunk-4WT6I4VG.js.map → chunk-GWB7ZD2A.js.map} +0 -0
  316. /package/dist/{chunk-JYZLDZ2R.js.map → chunk-HG2VVIKI.js.map} +0 -0
  317. /package/dist/{chunk-23ZQPP2G.js.map → chunk-HOSJESKO.js.map} +0 -0
  318. /package/dist/{chunk-EU6IRUCY.js.map → chunk-IDPZCHET.js.map} +0 -0
  319. /package/dist/{chunk-VMHCC7LO.js.map → chunk-IO2UQVR2.js.map} +0 -0
  320. /package/dist/{chunk-BWXKJDDH.js.map → chunk-ISMZ5UNE.js.map} +0 -0
  321. /package/dist/{chunk-SX4QDWXK.js.map → chunk-IWUFKS4J.js.map} +0 -0
  322. /package/dist/{chunk-I27WOMJ7.js.map → chunk-IY7DGT2Z.js.map} +0 -0
  323. /package/dist/{chunk-STBX5X6B.js.map → chunk-JAYNUYSJ.js.map} +0 -0
  324. /package/dist/{chunk-N34ZW6E4.js.map → chunk-K2REHR42.js.map} +0 -0
  325. /package/dist/{chunk-UJPGQCXD.js.map → chunk-KFKFK6G6.js.map} +0 -0
  326. /package/dist/{chunk-2HLMFRZ7.js.map → chunk-LDEKVBVO.js.map} +0 -0
  327. /package/dist/{chunk-MPZYCRC6.js.map → chunk-M236YHUH.js.map} +0 -0
  328. /package/dist/{chunk-L5VCT7LU.js.map → chunk-MCLZM5G7.js.map} +0 -0
  329. /package/dist/{chunk-7Z3DLNSD.js.map → chunk-MGKH5KO3.js.map} +0 -0
  330. /package/dist/{chunk-PUDYANAX.js.map → chunk-MMBLOJJB.js.map} +0 -0
  331. /package/dist/{chunk-EFHNFTWB.js.map → chunk-MND4YJSX.js.map} +0 -0
  332. /package/dist/{chunk-HJQXHLQQ.js.map → chunk-MROA6OFU.js.map} +0 -0
  333. /package/dist/{chunk-BQOGQULP.js.map → chunk-MZZEMPCI.js.map} +0 -0
  334. /package/dist/{chunk-ERAVI25Z.js.map → chunk-NAPHQ2MG.js.map} +0 -0
  335. /package/dist/{chunk-L5TJWQEF.js.map → chunk-NB2YX6X2.js.map} +0 -0
  336. /package/dist/{chunk-IHGLQJW6.js.map → chunk-NCPR2FOJ.js.map} +0 -0
  337. /package/dist/{chunk-UKWRCQWT.js.map → chunk-NOMHXZD7.js.map} +0 -0
  338. /package/dist/{chunk-DF2G4IXN.js.map → chunk-NPO4ZQFY.js.map} +0 -0
  339. /package/dist/{chunk-LWFWZTWX.js.map → chunk-NQV6G6MU.js.map} +0 -0
  340. /package/dist/{chunk-5HT35H4L.js.map → chunk-NRLWHZVS.js.map} +0 -0
  341. /package/dist/{chunk-GCZHKBY2.js.map → chunk-OANSZGAG.js.map} +0 -0
  342. /package/dist/{chunk-BHQPOCQE.js.map → chunk-OIIAIY7J.js.map} +0 -0
  343. /package/dist/{chunk-FFGHFU7A.js.map → chunk-OXX4ORRL.js.map} +0 -0
  344. /package/dist/{chunk-RTTPBBAN.js.map → chunk-OXYXMGRH.js.map} +0 -0
  345. /package/dist/{chunk-K7BN34IA.js.map → chunk-PL26BCRN.js.map} +0 -0
  346. /package/dist/{chunk-PM72NSEW.js.map → chunk-PYCTZ2HX.js.map} +0 -0
  347. /package/dist/{chunk-AQ52ZFBC.js.map → chunk-PZEADMX4.js.map} +0 -0
  348. /package/dist/{chunk-QPH52AZB.js.map → chunk-QNDOTDJK.js.map} +0 -0
  349. /package/dist/{chunk-LZACDIM2.js.map → chunk-RBGRQEW3.js.map} +0 -0
  350. /package/dist/{chunk-H3AW6QZF.js.map → chunk-RRJ462HP.js.map} +0 -0
  351. /package/dist/{chunk-V4JZCTMK.js.map → chunk-TKBW4YLS.js.map} +0 -0
  352. /package/dist/{chunk-4D2YH47Z.js.map → chunk-TRW7N4FS.js.map} +0 -0
  353. /package/dist/{chunk-OQJKY3Z5.js.map → chunk-UAAAXSKQ.js.map} +0 -0
  354. /package/dist/{chunk-S36KJNQH.js.map → chunk-UM73Q46J.js.map} +0 -0
  355. /package/dist/{chunk-7EJCAFMS.js.map → chunk-UTXYOWDR.js.map} +0 -0
  356. /package/dist/{chunk-AYHPSYZI.js.map → chunk-UUYAW3JH.js.map} +0 -0
  357. /package/dist/{chunk-MSMGQDVW.js.map → chunk-UVUBLLM3.js.map} +0 -0
  358. /package/dist/{chunk-JZIZBJCW.js.map → chunk-V4FI2TG6.js.map} +0 -0
  359. /package/dist/{chunk-MO6N4QYY.js.map → chunk-VARNSLUW.js.map} +0 -0
  360. /package/dist/{chunk-WJAGO5LL.js.map → chunk-VEB7NEON.js.map} +0 -0
  361. /package/dist/{chunk-GMZNJZRP.js.map → chunk-VLBFSD7B.js.map} +0 -0
  362. /package/dist/{chunk-JNIVZNIB.js.map → chunk-WKVL2G7X.js.map} +0 -0
  363. /package/dist/{chunk-KJXP6RUG.js.map → chunk-WNEVEMJ3.js.map} +0 -0
  364. /package/dist/{chunk-HNEOLXRF.js.map → chunk-WQIYGYY6.js.map} +0 -0
  365. /package/dist/{chunk-GQJKVQJR.js.map → chunk-WYLBEJQI.js.map} +0 -0
  366. /package/dist/{chunk-36DZ242S.js.map → chunk-XNROYP5E.js.map} +0 -0
  367. /package/dist/{chunk-XNAOCY67.js.map → chunk-XVZIBMSY.js.map} +0 -0
  368. /package/dist/{chunk-PXTLPC3P.js.map → chunk-YGVZ4QOZ.js.map} +0 -0
  369. /package/dist/{chunk-JX32ROLZ.js.map → chunk-YZTFQBYY.js.map} +0 -0
  370. /package/dist/{chunk-XSXM5LYZ.js.map → chunk-ZCRDYQL6.js.map} +0 -0
  371. /package/dist/{chunk-Z3XVYXQ3.js.map → chunk-ZXTAHSQU.js.map} +0 -0
  372. /package/dist/{resolution-ZU5BIAJA.js.map → entity-canonical-id-migration-GD5MPFUF.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "10.0.27",
3
+ "version": "10.0.29",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3347,7 +3347,7 @@
3347
3347
  "core"
3348
3348
  ],
3349
3349
  "peerDependencies": {
3350
- "@remnic/coding-graph": "^10.0.27"
3350
+ "@remnic/coding-graph": "^10.0.29"
3351
3351
  },
3352
3352
  "peerDependenciesMeta": {
3353
3353
  "@remnic/coding-graph": {
@@ -339,7 +339,10 @@ test("process-wide concurrency cap bounds in-flight requests across clients", as
339
339
  const config = createConfig({ localLlmUrl: "http://127.0.0.1:1007", localLlmMaxConcurrency: 2 });
340
340
  const clients = [new LocalLlmClient(config), new LocalLlmClient(config), new LocalLlmClient(config)];
341
341
  const results = await Promise.all(
342
- Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion([{ role: "user", content: `q${i}` }]))
342
+ Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion(
343
+ [{ role: "user", content: `q${i}` }],
344
+ { priority: i % 2 === 0 ? "recall-critical" : "background" }
345
+ ))
343
346
  );
344
347
  assert.equal(results.filter((r) => r?.content === "ok").length, 7);
345
348
  assert.equal(peak, 2);
@@ -404,3 +407,58 @@ test("waiting for a concurrency slot is bounded by the wait budget", async () =>
404
407
  }
405
408
  );
406
409
  });
410
+
411
+ test("limiter reserves one finite permit for recall-critical work", async () => {
412
+ const limiter = new LocalLlmConcurrencyLimiter(2);
413
+ const background = await limiter.acquire();
414
+ const secondBackground = await limiter.acquire({ timeoutMs: 0 });
415
+ try {
416
+ assert.ok(background);
417
+ assert.equal(secondBackground, null, "background must not consume the recall permit");
418
+ const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
419
+ assert.ok(foreground, "recall must start without waiting for background completion");
420
+ foreground();
421
+ foreground();
422
+ assert.equal(limiter.inFlight, 1, "release remains idempotent");
423
+ } finally {
424
+ secondBackground?.();
425
+ background?.();
426
+ }
427
+ });
428
+
429
+ test("reserved background queue survives aborts and responds to resizing", async () => {
430
+ const limiter = new LocalLlmConcurrencyLimiter(2);
431
+ const background = await limiter.acquire();
432
+ assert.ok(background);
433
+ const controller = new AbortController();
434
+ const aborted = limiter.acquire({ signal: controller.signal });
435
+ controller.abort();
436
+ const abortedRelease = await aborted;
437
+ abortedRelease?.();
438
+ assert.equal(abortedRelease, null);
439
+ const queued = limiter.acquire();
440
+ const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
441
+ assert.ok(foreground);
442
+ assert.equal(limiter.queued, 1);
443
+ limiter.setMax(3);
444
+ const nextBackground = await queued;
445
+ assert.ok(nextBackground);
446
+ assert.equal(limiter.inFlight, 3);
447
+ nextBackground();
448
+ background();
449
+ foreground();
450
+ assert.equal(limiter.inFlight, 0);
451
+ });
452
+
453
+ test("one-permit and unlimited limits still admit background work", async () => {
454
+ for (const max of [1, 0]) {
455
+ const limiter = new LocalLlmConcurrencyLimiter(max);
456
+ const first = await limiter.acquire({ timeoutMs: 0 });
457
+ assert.ok(first);
458
+ const second = await limiter.acquire({ timeoutMs: 0 });
459
+ assert.equal(second === null, max === 1);
460
+ second?.();
461
+ first();
462
+ assert.equal(limiter.inFlight, 0);
463
+ }
464
+ });
@@ -151,9 +151,10 @@ export function computeRateLimitDelayMs(
151
151
 
152
152
  type Waiter = { highPriority: boolean; grant: () => void };
153
153
 
154
- /** FIFO counting semaphore with a high-priority lane (recall-critical first). */
154
+ /** FIFO semaphore; finite caps above one reserve a permit for recall-critical work. */
155
155
  export class LocalLlmConcurrencyLimiter {
156
156
  private active = 0;
157
+ private activeBackground = 0;
157
158
  private max: number;
158
159
  private readonly waiters: Waiter[] = [];
159
160
 
@@ -182,20 +183,25 @@ export class LocalLlmConcurrencyLimiter {
182
183
  options: { highPriority?: boolean; signal?: AbortSignal; timeoutMs?: number } = {}
183
184
  ): Promise<(() => void) | null> {
184
185
  const { signal, timeoutMs } = options;
186
+ const highPriority = options.highPriority === true;
185
187
  if (signal?.aborted) return Promise.resolve(null);
186
- if (this.max <= 0 || (this.active < this.max && this.waiters.length === 0)) {
188
+ if (
189
+ this.canAcquire(highPriority) &&
190
+ (this.waiters.length === 0 || (highPriority && !this.waiters[0].highPriority))
191
+ ) {
187
192
  this.active += 1;
188
- return Promise.resolve(this.releaser());
193
+ if (!highPriority) this.activeBackground += 1;
194
+ return Promise.resolve(this.releaser(highPriority));
189
195
  }
190
196
  if (timeoutMs !== undefined && timeoutMs <= 0) return Promise.resolve(null);
191
197
  return new Promise((resolve) => {
192
198
  let timer: NodeJS.Timeout | undefined;
193
199
  const waiter: Waiter = {
194
- highPriority: options.highPriority === true,
200
+ highPriority,
195
201
  grant: () => {
196
202
  signal?.removeEventListener("abort", onAbort);
197
203
  if (timer) clearTimeout(timer);
198
- resolve(this.releaser());
204
+ resolve(this.releaser(highPriority));
199
205
  },
200
206
  };
201
207
  const onAbort = (): void => {
@@ -217,21 +223,30 @@ export class LocalLlmConcurrencyLimiter {
217
223
  });
218
224
  }
219
225
 
220
- private releaser(): () => void {
226
+ private canAcquire(highPriority: boolean): boolean {
227
+ return this.max <= 0 || (
228
+ this.active < this.max &&
229
+ (highPriority || this.max === 1 || this.activeBackground < this.max - 1)
230
+ );
231
+ }
232
+
233
+ private releaser(highPriority: boolean): () => void {
221
234
  let released = false;
222
235
  return () => {
223
236
  if (released) return;
224
237
  released = true;
225
238
  this.active -= 1;
239
+ if (!highPriority) this.activeBackground -= 1;
226
240
  this.pump();
227
241
  };
228
242
  }
229
243
 
230
244
  private pump(): void {
231
- while (this.waiters.length > 0 && (this.max <= 0 || this.active < this.max)) {
245
+ while (this.waiters.length > 0 && this.canAcquire(this.waiters[0].highPriority)) {
232
246
  const next = this.waiters.shift();
233
247
  if (!next) break;
234
248
  this.active += 1;
249
+ if (!next.highPriority) this.activeBackground += 1;
235
250
  next.grant();
236
251
  }
237
252
  }
@@ -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