@remnic/core 9.27.0 → 9.28.0

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 (439) hide show
  1. package/dist/access-admin-ops-surface.d.ts +13 -10
  2. package/dist/access-admin-ops-surface.js +43 -38
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +13 -10
  5. package/dist/access-authorization-probe.js +44 -39
  6. package/dist/access-boundary.d.ts +13 -10
  7. package/dist/access-boundary.js +43 -38
  8. package/dist/access-cli.js +100 -94
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-health-types.d.ts +10 -1
  11. package/dist/access-http-lcm-compaction.d.ts +13 -10
  12. package/dist/access-http.d.ts +13 -10
  13. package/dist/access-http.js +53 -48
  14. package/dist/access-identity-continuity-surface.d.ts +9 -8
  15. package/dist/access-identity-continuity-surface.js +43 -38
  16. package/dist/access-lcm-surface.d.ts +13 -10
  17. package/dist/access-lcm-surface.js +43 -38
  18. package/dist/access-mcp.d.ts +13 -10
  19. package/dist/access-mcp.js +48 -43
  20. package/dist/access-memory-search-fanout.d.ts +2 -2
  21. package/dist/{access-namespace-preflight-wRSZEO5V.d.ts → access-namespace-preflight-BiQ_-iUJ.d.ts} +1 -1
  22. package/dist/access-namespace-preflight.d.ts +3 -3
  23. package/dist/access-namespace-preflight.js +43 -38
  24. package/dist/access-observe-write-surface.d.ts +13 -10
  25. package/dist/access-observe-write-surface.js +43 -38
  26. package/dist/access-operations-batch.js +45 -40
  27. package/dist/access-operations.d.ts +16 -13
  28. package/dist/access-operations.js +47 -42
  29. package/dist/access-recall-concurrency.d.ts +13 -10
  30. package/dist/access-recall-concurrency.js +43 -38
  31. package/dist/access-recall-response.d.ts +13 -10
  32. package/dist/access-recall-response.js +43 -38
  33. package/dist/access-recall-surface.d.ts +13 -10
  34. package/dist/access-recall-surface.js +43 -38
  35. package/dist/access-schema.d.ts +80 -80
  36. package/dist/access-schema.js +4 -4
  37. package/dist/{access-service-B4-Fu9x2.d.ts → access-service-CXkIgcGh.d.ts} +12 -8
  38. package/dist/access-service.d.ts +13 -10
  39. package/dist/access-service.js +43 -38
  40. package/dist/access-surface-catalog.d.ts +13 -10
  41. package/dist/access-wearables-meetings-surface.d.ts +4 -3
  42. package/dist/action-confidence.d.ts +1 -1
  43. package/dist/active-memory-bridge.d.ts +1 -1
  44. package/dist/active-recall.d.ts +1 -1
  45. package/dist/active-recall.js +4 -2
  46. package/dist/active-recall.js.map +1 -1
  47. package/dist/adapters/index.js +7 -7
  48. package/dist/adapters/registry.js +3 -3
  49. package/dist/behavior-learner.d.ts +1 -1
  50. package/dist/behavior-signals.d.ts +1 -1
  51. package/dist/bootstrap.d.ts +9 -8
  52. package/dist/briefing.d.ts +3 -2
  53. package/dist/briefing.js +12 -10
  54. package/dist/buffer-surprise-report.d.ts +1 -1
  55. package/dist/buffer.d.ts +3 -2
  56. package/dist/bulk-import/index.d.ts +3 -3
  57. package/dist/calibration.d.ts +1 -1
  58. package/dist/capabilities.d.ts +1 -1
  59. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
  60. package/dist/{catalog-BLLxZ5L5.d.ts → catalog-Bouj8Gfg.d.ts} +1 -1
  61. package/dist/causal-behavior.d.ts +1 -1
  62. package/dist/causal-consolidation.d.ts +1 -1
  63. package/dist/causal-consolidation.js +13 -11
  64. package/dist/causal-consolidation.js.map +1 -1
  65. package/dist/causal-trajectory-graph.d.ts +1 -1
  66. package/dist/{chunk-7RZJWZ46.js → chunk-227UFSEQ.js} +5 -5
  67. package/dist/{chunk-UT3OQPFA.js → chunk-2BEJ4XCF.js} +2 -2
  68. package/dist/{chunk-EOLC6EXV.js → chunk-2FYNLDKJ.js} +24 -3
  69. package/dist/chunk-2FYNLDKJ.js.map +1 -0
  70. package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
  71. package/dist/{chunk-6NNNURLK.js → chunk-2PHX3I5C.js} +8 -8
  72. package/dist/{chunk-HORJVJ6K.js → chunk-2TQ2UFKH.js} +65 -60
  73. package/dist/chunk-2TQ2UFKH.js.map +1 -0
  74. package/dist/{chunk-RPA2RX5P.js → chunk-3BSJBIZ3.js} +2 -2
  75. package/dist/{chunk-JCVQV6QX.js → chunk-3UOSVTFR.js} +11 -11
  76. package/dist/{chunk-AJ7QCSIS.js → chunk-7CPOZH6Y.js} +2 -2
  77. package/dist/{chunk-MEBQFQ26.js → chunk-AHFZXUZM.js} +7 -5
  78. package/dist/chunk-AHFZXUZM.js.map +1 -0
  79. package/dist/{chunk-U3TH5Q2Q.js → chunk-ALTP3MFS.js} +2 -2
  80. package/dist/{chunk-77CNV7DY.js → chunk-BLAKS3CP.js} +4 -4
  81. package/dist/{chunk-XQ4EH6DI.js → chunk-BR4UFMLO.js} +2 -2
  82. package/dist/chunk-CVILPI77.js +432 -0
  83. package/dist/chunk-CVILPI77.js.map +1 -0
  84. package/dist/{chunk-WEAO45M4.js → chunk-DKQM5Q6Q.js} +2 -2
  85. package/dist/{chunk-CSXHRAJJ.js → chunk-EG64LXKL.js} +3 -3
  86. package/dist/{chunk-PTN6UAUA.js → chunk-ES36HTSC.js} +4 -4
  87. package/dist/{chunk-5BRT4Z2I.js → chunk-EZWWCN7I.js} +2 -2
  88. package/dist/{chunk-XKGPIWGI.js → chunk-FBZTWUK5.js} +5 -5
  89. package/dist/{chunk-GJJBPI6I.js → chunk-FOCEI3TN.js} +6 -3
  90. package/dist/chunk-FOCEI3TN.js.map +1 -0
  91. package/dist/{chunk-FRVTCCGD.js → chunk-GJPCP5CA.js} +2 -2
  92. package/dist/{chunk-CI26JDZA.js → chunk-GQSMYXKL.js} +6 -4
  93. package/dist/{chunk-CI26JDZA.js.map → chunk-GQSMYXKL.js.map} +1 -1
  94. package/dist/{chunk-TGQ2NTWH.js → chunk-IJ6WHMFB.js} +16 -2
  95. package/dist/{chunk-TGQ2NTWH.js.map → chunk-IJ6WHMFB.js.map} +1 -1
  96. package/dist/{chunk-VDX2J7OX.js → chunk-IPLYGWQF.js} +6 -6
  97. package/dist/{chunk-YSGBD3QN.js → chunk-IYT632D5.js} +3 -3
  98. package/dist/{chunk-2QSZNTDO.js → chunk-JBPKEARU.js} +7 -7
  99. package/dist/{chunk-RSYJV25N.js → chunk-JIF3VRKQ.js} +8 -8
  100. package/dist/{chunk-V25OWXAC.js → chunk-JQ7T5VDC.js} +1 -1
  101. package/dist/chunk-JQ7T5VDC.js.map +1 -0
  102. package/dist/{chunk-EUSLKI4X.js → chunk-KKBTBUVT.js} +2 -2
  103. package/dist/{chunk-RNY3BO35.js → chunk-LIHC3T43.js} +2 -2
  104. package/dist/{chunk-RGJBM7Z3.js → chunk-MJPV4LBH.js} +79 -79
  105. package/dist/{chunk-RGJBM7Z3.js.map → chunk-MJPV4LBH.js.map} +1 -1
  106. package/dist/{chunk-UGH6VPPO.js → chunk-MWTMTW2T.js} +2 -2
  107. package/dist/{chunk-MG6MNSP4.js → chunk-MXJ3VP6S.js} +6 -6
  108. package/dist/{chunk-XKO7BM2C.js → chunk-NBNDVNJ2.js} +186 -25
  109. package/dist/chunk-NBNDVNJ2.js.map +1 -0
  110. package/dist/{chunk-M5EPMNS4.js → chunk-P2CLYN6H.js} +2 -2
  111. package/dist/{chunk-BNGPHGVZ.js → chunk-PXQM2HOG.js} +13 -6
  112. package/dist/{chunk-BNGPHGVZ.js.map → chunk-PXQM2HOG.js.map} +1 -1
  113. package/dist/{chunk-CIQXI47W.js → chunk-Q5VJUNXA.js} +1192 -268
  114. package/dist/chunk-Q5VJUNXA.js.map +1 -0
  115. package/dist/{chunk-KGTD2KZJ.js → chunk-Q6YJW6KE.js} +2 -2
  116. package/dist/{chunk-X3SQYAPP.js → chunk-R372P2EH.js} +2 -2
  117. package/dist/{chunk-4MJZTF42.js → chunk-RKXZ7P4V.js} +3 -3
  118. package/dist/chunk-RMMVQQA6.js +70 -0
  119. package/dist/chunk-RMMVQQA6.js.map +1 -0
  120. package/dist/{chunk-2RAC4JIG.js → chunk-S24E4MBQ.js} +14 -14
  121. package/dist/chunk-S24E4MBQ.js.map +1 -0
  122. package/dist/{chunk-RYHWZ52A.js → chunk-S2BBISHU.js} +4 -4
  123. package/dist/{chunk-LLISELSY.js → chunk-TBMRQZDY.js} +2 -2
  124. package/dist/{chunk-FMLKOL2Z.js → chunk-TC3VNKGX.js} +4 -4
  125. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  126. package/dist/{chunk-NUWMZIYZ.js → chunk-UR5SIN7H.js} +53 -35
  127. package/dist/chunk-UR5SIN7H.js.map +1 -0
  128. package/dist/{chunk-JDOJZTFF.js → chunk-VFNI3RPT.js} +2 -2
  129. package/dist/chunk-VNA7W7QF.js +134 -0
  130. package/dist/chunk-VNA7W7QF.js.map +1 -0
  131. package/dist/{chunk-EYJSW2RV.js → chunk-VYYNBJXH.js} +5 -5
  132. package/dist/chunk-WNIUPHWV.js +152 -0
  133. package/dist/chunk-WNIUPHWV.js.map +1 -0
  134. package/dist/chunk-WRJVB3C2.js +118 -0
  135. package/dist/chunk-WRJVB3C2.js.map +1 -0
  136. package/dist/{chunk-BHLN7L7M.js → chunk-WVVK2RFM.js} +6 -6
  137. package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
  138. package/dist/{chunk-HEXSNYJD.js → chunk-YOBNCD3Z.js} +6 -6
  139. package/dist/chunk-YXZG5TVN.js +40 -0
  140. package/dist/chunk-YXZG5TVN.js.map +1 -0
  141. package/dist/{chunk-4DJQYKMN.js → chunk-ZIKENA2C.js} +2 -2
  142. package/dist/chunk-ZIKENA2C.js.map +1 -0
  143. package/dist/{chunk-SK3EYPKC.js → chunk-ZN4UZWMT.js} +2 -2
  144. package/dist/{cli-Bz7X7dGx.d.ts → cli-DJbkaZBU.d.ts} +5 -5
  145. package/dist/cli.d.ts +14 -12
  146. package/dist/cli.js +79 -74
  147. package/dist/compounding/engine.d.ts +3 -2
  148. package/dist/compounding/engine.js +12 -10
  149. package/dist/compounding/preference-consolidator.d.ts +1 -1
  150. package/dist/compression-optimizer.d.ts +1 -1
  151. package/dist/config.d.ts +1 -1
  152. package/dist/config.js +4 -2
  153. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  154. package/dist/connectors/codex-materialize-runner.js +12 -10
  155. package/dist/connectors/codex-materialize.d.ts +1 -1
  156. package/dist/connectors/index.d.ts +1 -1
  157. package/dist/connectors/index.js +12 -10
  158. package/dist/consolidation-provenance-check.d.ts +3 -2
  159. package/dist/consolidation-undo.d.ts +3 -2
  160. package/dist/contradiction/index.d.ts +3 -2
  161. package/dist/conversation-index/backend.d.ts +1 -1
  162. package/dist/conversation-index/backend.js +2 -2
  163. package/dist/conversation-index/chunker.d.ts +1 -1
  164. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  165. package/dist/conversation-index/indexer.d.ts +1 -1
  166. package/dist/conversation-index/search.d.ts +1 -1
  167. package/dist/corpus-watermark.d.ts +15 -2
  168. package/dist/corpus-watermark.js +25 -21
  169. package/dist/day-summary.d.ts +1 -1
  170. package/dist/delinearize.d.ts +1 -1
  171. package/dist/direct-answer-wiring.d.ts +1 -1
  172. package/dist/direct-answer.d.ts +1 -1
  173. package/dist/embedding-fallback.d.ts +1 -1
  174. package/dist/enrichment/index.d.ts +1 -1
  175. package/dist/entity-id-normalization.d.ts +4 -0
  176. package/dist/entity-id-normalization.js +10 -0
  177. package/dist/entity-id-normalization.js.map +1 -0
  178. package/dist/entity-retrieval-boundaries.d.ts +3 -0
  179. package/dist/entity-retrieval-boundaries.js +8 -0
  180. package/dist/entity-retrieval-boundaries.js.map +1 -0
  181. package/dist/entity-retrieval.d.ts +3 -2
  182. package/dist/entity-retrieval.js +13 -10
  183. package/dist/entity-schema.d.ts +1 -1
  184. package/dist/entity-schema.js +1 -1
  185. package/dist/explicit-capture.d.ts +9 -8
  186. package/dist/extraction-error-classification.d.ts +1 -1
  187. package/dist/extraction-faithfulness.d.ts +1 -1
  188. package/dist/extraction-judge-telemetry.d.ts +1 -1
  189. package/dist/extraction-judge-training.d.ts +1 -1
  190. package/dist/extraction-judge.d.ts +1 -1
  191. package/dist/extraction-liveness.d.ts +1 -1
  192. package/dist/extraction.d.ts +1 -1
  193. package/dist/fallback-llm.d.ts +1 -1
  194. package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
  195. package/dist/graph-dashboard-diff.d.ts +1 -1
  196. package/dist/graph-dashboard-key.d.ts +1 -1
  197. package/dist/graph-dashboard-parser.d.ts +1 -1
  198. package/dist/graph-edge-reinforcement.d.ts +1 -1
  199. package/dist/graph-snapshot.d.ts +1 -1
  200. package/dist/graph.d.ts +1 -1
  201. package/dist/importance.d.ts +1 -1
  202. package/dist/importers/index.d.ts +1 -1
  203. package/dist/in-flight-reads.d.ts +1 -1
  204. package/dist/index.d.ts +409 -407
  205. package/dist/index.js +147 -141
  206. package/dist/intent.d.ts +1 -1
  207. package/dist/lcm/engine.d.ts +1 -1
  208. package/dist/lcm/engine.js +3 -3
  209. package/dist/lcm/index.d.ts +1 -1
  210. package/dist/lcm/index.js +9 -9
  211. package/dist/lcm/tools.d.ts +1 -1
  212. package/dist/lifecycle.d.ts +1 -1
  213. package/dist/live-connectors-runner.d.ts +1 -1
  214. package/dist/local-llm.d.ts +1 -1
  215. package/dist/local-model-endpoint.d.ts +1 -1
  216. package/dist/maintenance/memory-governance.d.ts +1 -1
  217. package/dist/maintenance/memory-governance.js +12 -10
  218. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
  219. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
  220. package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
  221. package/dist/maintenance/rebuild-memory-projection.js +13 -11
  222. package/dist/{maintenance-DgnD2z8S.d.ts → maintenance-DveW6azo.d.ts} +3 -3
  223. package/dist/mcp-memory-inspector-app.d.ts +13 -10
  224. package/dist/memory-action-policy.d.ts +1 -1
  225. package/dist/memory-cache.d.ts +1 -1
  226. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  227. package/dist/memory-projection-mutations.d.ts +4 -0
  228. package/dist/memory-projection-mutations.js +15 -0
  229. package/dist/memory-projection-mutations.js.map +1 -0
  230. package/dist/memory-projection-store.d.ts +1 -1
  231. package/dist/memory-provenance.d.ts +1 -1
  232. package/dist/memory-worth-outcomes.d.ts +3 -2
  233. package/dist/models-json.d.ts +1 -1
  234. package/dist/namespaces/migrate.d.ts +4 -3
  235. package/dist/namespaces/migrate.js +22 -20
  236. package/dist/namespaces/principal.d.ts +1 -1
  237. package/dist/namespaces/search.d.ts +1 -1
  238. package/dist/namespaces/search.js +9 -9
  239. package/dist/namespaces/storage.d.ts +4 -3
  240. package/dist/namespaces/storage.js +12 -10
  241. package/dist/native-knowledge.d.ts +1 -1
  242. package/dist/offline-sync-impression-drain.d.ts +1 -1
  243. package/dist/offline-sync-impression-drain.js +3 -3
  244. package/dist/operator-doctor-corpus.d.ts +22 -3
  245. package/dist/operator-doctor-corpus.js +32 -22
  246. package/dist/operator-doctor-replica.d.ts +52 -0
  247. package/dist/operator-doctor-replica.js +15 -0
  248. package/dist/operator-doctor-replica.js.map +1 -0
  249. package/dist/operator-toolkit.d.ts +7 -3
  250. package/dist/operator-toolkit.js +33 -27
  251. package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
  252. package/dist/orchestration/maintenance.d.ts +5 -4
  253. package/dist/orchestration/maintenance.js +17 -15
  254. package/dist/{orchestrator-rIHmHpv1.d.ts → orchestrator-Cvlgpe6I.d.ts} +7 -7
  255. package/dist/orchestrator.d.ts +9 -8
  256. package/dist/orchestrator.js +99 -93
  257. package/dist/patterns-cli.d.ts +1 -1
  258. package/dist/{pipeline-Bq5zU6Bp.d.ts → pipeline-BveYVaz7.d.ts} +1 -1
  259. package/dist/policy-runtime.d.ts +1 -1
  260. package/dist/proactive-contention.d.ts +1 -1
  261. package/dist/provenance.d.ts +1 -1
  262. package/dist/{qmd-3ILJkC70.d.ts → qmd-CLWDsbYR.d.ts} +1 -1
  263. package/dist/qmd-preflight.d.ts +2 -2
  264. package/dist/qmd-recall-cache.d.ts +1 -1
  265. package/dist/qmd.d.ts +2 -2
  266. package/dist/recall-concurrency-config.d.ts +1 -1
  267. package/dist/recall-disclosure-escalation.d.ts +1 -1
  268. package/dist/recall-explain-renderer.d.ts +1 -1
  269. package/dist/recall-explain-renderer.js +3 -3
  270. package/dist/recall-memory-map.d.ts +1 -1
  271. package/dist/recall-planner-llm.d.ts +1 -1
  272. package/dist/recall-state.d.ts +1 -1
  273. package/dist/recall-state.js +2 -2
  274. package/dist/recall-tag-filter.d.ts +1 -1
  275. package/dist/recall-timings.d.ts +1 -1
  276. package/dist/recall-xray-cli.d.ts +1 -1
  277. package/dist/recall-xray-cli.js +4 -4
  278. package/dist/recall-xray-renderer.d.ts +1 -1
  279. package/dist/recall-xray-renderer.js +3 -3
  280. package/dist/recall-xray.d.ts +1 -1
  281. package/dist/recall-xray.js +2 -2
  282. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  283. package/dist/replay/normalizers/claude.d.ts +1 -1
  284. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  285. package/dist/replay/normalizers/shared.d.ts +1 -1
  286. package/dist/replay/runner.d.ts +1 -1
  287. package/dist/replay/types.d.ts +1 -1
  288. package/dist/replica-divergence.d.ts +279 -0
  289. package/dist/replica-divergence.js +28 -0
  290. package/dist/replica-divergence.js.map +1 -0
  291. package/dist/replica-peers-config.d.ts +5 -0
  292. package/dist/replica-peers-config.js +13 -0
  293. package/dist/replica-peers-config.js.map +1 -0
  294. package/dist/resolve-auth-token.d.ts +21 -2
  295. package/dist/resolve-auth-token.js +5 -1
  296. package/dist/resume-bundles.js +5 -3
  297. package/dist/retrieval-agents.d.ts +2 -2
  298. package/dist/retrieval-tiers.d.ts +1 -1
  299. package/dist/routing/engine.d.ts +1 -1
  300. package/dist/routing/store.d.ts +1 -1
  301. package/dist/salvage-envelope.d.ts +1 -1
  302. package/dist/schemas.d.ts +76 -76
  303. package/dist/{scope-profiles-D0-ff4xT.d.ts → scope-profiles-BVf3DZ7z.d.ts} +1 -1
  304. package/dist/search/embed-helper.d.ts +1 -1
  305. package/dist/search/factory.d.ts +1 -1
  306. package/dist/search/factory.js +8 -8
  307. package/dist/search/index.d.ts +1 -1
  308. package/dist/search/index.js +13 -13
  309. package/dist/search/lancedb-backend.d.ts +1 -1
  310. package/dist/search/lancedb-backend.js +2 -2
  311. package/dist/search/meilisearch-backend.d.ts +1 -1
  312. package/dist/search/meilisearch-backend.js +2 -2
  313. package/dist/search/noop-backend.d.ts +1 -1
  314. package/dist/search/orama-backend.d.ts +1 -1
  315. package/dist/search/orama-backend.js +2 -2
  316. package/dist/search/port.d.ts +1 -1
  317. package/dist/search/remote-backend.d.ts +1 -1
  318. package/dist/{semantic-consolidation-4Tfh7c1g.d.ts → semantic-consolidation-DXLV47y2.d.ts} +1 -1
  319. package/dist/semantic-consolidation.d.ts +2 -2
  320. package/dist/semantic-consolidation.js +13 -11
  321. package/dist/semantic-rule-promotion.js +12 -10
  322. package/dist/semantic-rule-verifier.d.ts +1 -1
  323. package/dist/semantic-rule-verifier.js +12 -10
  324. package/dist/{service-cTvil7D_.d.ts → service-DbgPTeMJ.d.ts} +2 -2
  325. package/dist/session-observer-bands.d.ts +1 -1
  326. package/dist/session-observer-state.d.ts +1 -1
  327. package/dist/shared-context/manager.d.ts +9 -9
  328. package/dist/signal.d.ts +1 -1
  329. package/dist/{storage-C9ibpTme.d.ts → storage-DOzTm8wG.d.ts} +13 -19
  330. package/dist/storage.d.ts +3 -2
  331. package/dist/storage.js +13 -10
  332. package/dist/summarizer.d.ts +1 -1
  333. package/dist/summary-snapshot.d.ts +1 -1
  334. package/dist/temporal-supersession.d.ts +3 -2
  335. package/dist/temporal-timeline-recall.d.ts +1 -1
  336. package/dist/temporal-validity.d.ts +1 -1
  337. package/dist/threading.d.ts +1 -1
  338. package/dist/tier-migration.d.ts +3 -2
  339. package/dist/tier-routing.d.ts +1 -1
  340. package/dist/topics.d.ts +1 -1
  341. package/dist/transcript.d.ts +1 -1
  342. package/dist/transfer/autodetect.js +1 -1
  343. package/dist/transfer/backup.js +2 -2
  344. package/dist/transfer/capsule-export.js +3 -3
  345. package/dist/transfer/capsule-import.js +2 -2
  346. package/dist/transfer/types.d.ts +66 -66
  347. package/dist/trust-score-stage.d.ts +1 -1
  348. package/dist/trust-score.d.ts +1 -1
  349. package/dist/{types-DHBxIF0s.d.ts → types-BA7W-_xy.d.ts} +53 -4
  350. package/dist/types.d.ts +1 -1
  351. package/dist/types.js +1 -1
  352. package/dist/utility-runtime.d.ts +1 -1
  353. package/dist/verified-recall.js +12 -10
  354. package/dist/write-envelope.d.ts +1 -1
  355. package/package.json +2 -2
  356. package/src/access-health-types.ts +8 -0
  357. package/src/access-http.test.ts +4 -0
  358. package/src/access-service-health.test.ts +23 -0
  359. package/src/access-service.ts +27 -4
  360. package/src/cli.ts +9 -7
  361. package/src/config.ts +2 -1
  362. package/src/corpus-watermark.test.ts +21 -0
  363. package/src/corpus-watermark.ts +25 -3
  364. package/src/entity-id-normalization.ts +55 -0
  365. package/src/entity-retrieval-boundaries.ts +134 -0
  366. package/src/entity-retrieval.ts +253 -27
  367. package/src/entity-schema.ts +2 -1
  368. package/src/memory-projection-mutations.ts +132 -0
  369. package/src/namespaces/storage.ts +5 -3
  370. package/src/operator-doctor-corpus.ts +41 -0
  371. package/src/operator-doctor-replica.test.ts +261 -0
  372. package/src/operator-doctor-replica.ts +129 -0
  373. package/src/operator-toolkit.ts +5 -4
  374. package/src/orchestration/orchestrator-init.ts +2 -2
  375. package/src/replica-divergence.test.ts +1475 -0
  376. package/src/replica-divergence.ts +910 -0
  377. package/src/replica-peers-config.ts +250 -0
  378. package/src/resolve-auth-token.ts +38 -0
  379. package/src/storage/entity-canonical-id-migration-adapter.ts +72 -0
  380. package/src/storage/entity-canonical-id-migration-runner.ts +68 -0
  381. package/src/storage/entity-canonical-id-migration.ts +904 -0
  382. package/src/storage/entity-store.ts +44 -0
  383. package/src/storage/memory-frontmatter-metadata.ts +17 -0
  384. package/src/storage/memory-migration-serialization.ts +37 -0
  385. package/src/storage/memory-read-store.ts +3 -2
  386. package/src/storage/profile-header.ts +8 -7
  387. package/src/storage-profile-timestamp.test.ts +68 -0
  388. package/src/storage.ts +68 -69
  389. package/src/types.ts +3 -3
  390. package/dist/chunk-2RAC4JIG.js.map +0 -1
  391. package/dist/chunk-4DJQYKMN.js.map +0 -1
  392. package/dist/chunk-CIQXI47W.js.map +0 -1
  393. package/dist/chunk-EOLC6EXV.js.map +0 -1
  394. package/dist/chunk-GJJBPI6I.js.map +0 -1
  395. package/dist/chunk-HORJVJ6K.js.map +0 -1
  396. package/dist/chunk-MEBQFQ26.js.map +0 -1
  397. package/dist/chunk-NUWMZIYZ.js.map +0 -1
  398. package/dist/chunk-V25OWXAC.js.map +0 -1
  399. package/dist/chunk-XKO7BM2C.js.map +0 -1
  400. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  401. /package/dist/{chunk-7RZJWZ46.js.map → chunk-227UFSEQ.js.map} +0 -0
  402. /package/dist/{chunk-UT3OQPFA.js.map → chunk-2BEJ4XCF.js.map} +0 -0
  403. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
  404. /package/dist/{chunk-6NNNURLK.js.map → chunk-2PHX3I5C.js.map} +0 -0
  405. /package/dist/{chunk-RPA2RX5P.js.map → chunk-3BSJBIZ3.js.map} +0 -0
  406. /package/dist/{chunk-JCVQV6QX.js.map → chunk-3UOSVTFR.js.map} +0 -0
  407. /package/dist/{chunk-AJ7QCSIS.js.map → chunk-7CPOZH6Y.js.map} +0 -0
  408. /package/dist/{chunk-U3TH5Q2Q.js.map → chunk-ALTP3MFS.js.map} +0 -0
  409. /package/dist/{chunk-77CNV7DY.js.map → chunk-BLAKS3CP.js.map} +0 -0
  410. /package/dist/{chunk-XQ4EH6DI.js.map → chunk-BR4UFMLO.js.map} +0 -0
  411. /package/dist/{chunk-WEAO45M4.js.map → chunk-DKQM5Q6Q.js.map} +0 -0
  412. /package/dist/{chunk-CSXHRAJJ.js.map → chunk-EG64LXKL.js.map} +0 -0
  413. /package/dist/{chunk-PTN6UAUA.js.map → chunk-ES36HTSC.js.map} +0 -0
  414. /package/dist/{chunk-5BRT4Z2I.js.map → chunk-EZWWCN7I.js.map} +0 -0
  415. /package/dist/{chunk-XKGPIWGI.js.map → chunk-FBZTWUK5.js.map} +0 -0
  416. /package/dist/{chunk-FRVTCCGD.js.map → chunk-GJPCP5CA.js.map} +0 -0
  417. /package/dist/{chunk-VDX2J7OX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  418. /package/dist/{chunk-YSGBD3QN.js.map → chunk-IYT632D5.js.map} +0 -0
  419. /package/dist/{chunk-2QSZNTDO.js.map → chunk-JBPKEARU.js.map} +0 -0
  420. /package/dist/{chunk-RSYJV25N.js.map → chunk-JIF3VRKQ.js.map} +0 -0
  421. /package/dist/{chunk-EUSLKI4X.js.map → chunk-KKBTBUVT.js.map} +0 -0
  422. /package/dist/{chunk-RNY3BO35.js.map → chunk-LIHC3T43.js.map} +0 -0
  423. /package/dist/{chunk-UGH6VPPO.js.map → chunk-MWTMTW2T.js.map} +0 -0
  424. /package/dist/{chunk-MG6MNSP4.js.map → chunk-MXJ3VP6S.js.map} +0 -0
  425. /package/dist/{chunk-M5EPMNS4.js.map → chunk-P2CLYN6H.js.map} +0 -0
  426. /package/dist/{chunk-KGTD2KZJ.js.map → chunk-Q6YJW6KE.js.map} +0 -0
  427. /package/dist/{chunk-X3SQYAPP.js.map → chunk-R372P2EH.js.map} +0 -0
  428. /package/dist/{chunk-4MJZTF42.js.map → chunk-RKXZ7P4V.js.map} +0 -0
  429. /package/dist/{chunk-RYHWZ52A.js.map → chunk-S2BBISHU.js.map} +0 -0
  430. /package/dist/{chunk-LLISELSY.js.map → chunk-TBMRQZDY.js.map} +0 -0
  431. /package/dist/{chunk-FMLKOL2Z.js.map → chunk-TC3VNKGX.js.map} +0 -0
  432. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  433. /package/dist/{chunk-JDOJZTFF.js.map → chunk-VFNI3RPT.js.map} +0 -0
  434. /package/dist/{chunk-EYJSW2RV.js.map → chunk-VYYNBJXH.js.map} +0 -0
  435. /package/dist/{chunk-BHLN7L7M.js.map → chunk-WVVK2RFM.js.map} +0 -0
  436. /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
  437. /package/dist/{chunk-HEXSNYJD.js.map → chunk-YOBNCD3Z.js.map} +0 -0
  438. /package/dist/{chunk-SK3EYPKC.js.map → chunk-ZN4UZWMT.js.map} +0 -0
  439. /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
@@ -0,0 +1,904 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ access,
4
+ lstat,
5
+ readFile,
6
+ readdir,
7
+ realpath,
8
+ rename,
9
+ stat as statFile,
10
+ unlink,
11
+ writeFile,
12
+ } from "node:fs/promises";
13
+ import path from "node:path";
14
+ import { readFileSync } from "node:fs";
15
+ import { z } from "zod";
16
+ import { computeSupersessionKey, normalizeSupersessionKey } from "../temporal-supersession.js";
17
+ import type { EntityFile, MemoryFile } from "../types.js";
18
+ import { isErrnoCode } from "../utils/errno.js";
19
+ import { withHeldFileLock } from "../utils/serialize-mutations.js";
20
+ import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
21
+ import { assertPathInsideRoot } from "../utils/path-containment.js";
22
+
23
+ const ENTITY_CANONICAL_ID_MIGRATION_VERSION = 1;
24
+ export const ENTITY_CANONICAL_ID_MIGRATION_FILE = "entity-canonical-id-migration-v1.json";
25
+ const ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS = 60_000;
26
+ const ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS = 300_000;
27
+ const TOMBSTONE_LOCK_STALE_MS = 30_000;
28
+ const TOMBSTONE_LOCK_MAX_WAIT_MS = 5_000;
29
+ const ENTITY_CANONICAL_ID_MUTATION_LOCK_STALE_MS = 60_000;
30
+ const ENTITY_CANONICAL_ID_MUTATION_LOCK_MAX_WAIT_MS = 300_000;
31
+ const MEMORY_REWRITE_MAX_PASSES = 3;
32
+ const ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;
33
+
34
+ export function loadHistoricalEntityCanonicalIds(stateDir: string): Readonly<Record<string, string>> {
35
+ const statePath = path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
36
+ try {
37
+ const parsed: unknown = JSON.parse(readFileSync(statePath, "utf-8"));
38
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
39
+ const mappings = (parsed as { mappings?: unknown }).mappings;
40
+ if (!mappings || typeof mappings !== "object" || Array.isArray(mappings)) return {};
41
+ const cleaned: Record<string, string> = {};
42
+ for (const [legacyId, canonicalId] of Object.entries(mappings)) {
43
+ if (legacyId.length > 0 && typeof canonicalId === "string" && canonicalId.length > 0) {
44
+ cleaned[legacyId] = canonicalId;
45
+ }
46
+ }
47
+ return cleaned;
48
+ } catch {
49
+ return {};
50
+ }
51
+ }
52
+
53
+ export function resolveHistoricalEntityCanonicalId(
54
+ normalized: string,
55
+ mappings: Readonly<Record<string, string>>,
56
+ ): string {
57
+ let current = normalized;
58
+ const seen = new Set<string>();
59
+ while (!seen.has(current)) {
60
+ seen.add(current);
61
+ const next = mappings[current];
62
+ if (!next || next === current) break;
63
+ current = next;
64
+ }
65
+ return current;
66
+ }
67
+
68
+ const EntityCanonicalIdMigrationStateSchema = z.object({
69
+ version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
70
+ complete: z.boolean(),
71
+ mappings: z.record(z.string().min(1)),
72
+ });
73
+
74
+ type EntityCanonicalIdMigrationState = z.infer<typeof EntityCanonicalIdMigrationStateSchema>;
75
+
76
+ export interface EntityCanonicalIdMigrationDependencies {
77
+ stateDir: string;
78
+ entitiesDir: string;
79
+ normalizeEntityName(raw: string, type: string): string;
80
+ resolveEntityFilePath(id: string): string | null;
81
+ readStorageSecureFile(filePath: string): Promise<string>;
82
+ writeStorageSecureFile(filePath: string, content: string, forceEncrypt?: boolean): Promise<void>;
83
+ isEncryptedStorageFile(filePath: string): Promise<boolean>;
84
+ snapshotBeforeWrite(filePath: string, trigger: "write"): Promise<void>;
85
+ parseEntityFile(content: string): EntityFile;
86
+ serializeEntityFile(entity: EntityFile): string;
87
+ serializeMemoryWithEntityRef(memory: MemoryFile, entityRef: string): string;
88
+ readMemoryByPath(filePath: string): Promise<MemoryFile | null>;
89
+ readAllMemories(): Promise<MemoryFile[]>;
90
+ readAllColdMemories(): Promise<MemoryFile[]>;
91
+ readArchivedMemories(): Promise<MemoryFile[]>;
92
+ validateMemoryScanRoots?(): Promise<void>;
93
+ rewriteProjectedEntityReferences(mappings: Readonly<Record<string, string>>): Promise<void>;
94
+ rewriteProjectedMemoryEntityReference(
95
+ memoryId: string,
96
+ previousEntityRef: string,
97
+ nextEntityRef: string,
98
+ ): Promise<void>;
99
+ invalidateKnowledgeIndexCache(): void;
100
+ invalidateAllMemoriesCache(): void;
101
+ invalidateColdMemoriesCache(): void;
102
+ bumpMemoryStatusVersion(): void;
103
+ readMigrationFingerprint?(): Promise<string>;
104
+ }
105
+
106
+ async function fingerprintPath(filePath: string): Promise<string> {
107
+ let fileStat;
108
+ try {
109
+ fileStat = await lstat(filePath);
110
+ } catch (error) {
111
+ if (!isErrnoCode(error, "ENOENT")) throw error;
112
+ }
113
+ return fileStat
114
+ ? `${fileStat.dev}:${fileStat.ino}:${fileStat.mtimeMs}:${fileStat.ctimeMs}:${fileStat.size}`
115
+ : "missing";
116
+ }
117
+
118
+ export async function getFingerprint(
119
+ baseDir: string,
120
+ entitiesDir: string,
121
+ getCorpusScanVersion: () => string,
122
+ ): Promise<string> {
123
+ const [entityFingerprint, aliasFingerprint] = await Promise.all([
124
+ fingerprintPath(entitiesDir),
125
+ fingerprintPath(path.join(baseDir, "config", "aliases.json")),
126
+ ]);
127
+ return `${entityFingerprint}:${aliasFingerprint}:${getCorpusScanVersion()}`;
128
+ }
129
+
130
+ export async function validateRoots(
131
+ baseDir: string,
132
+ entitiesDir: string,
133
+ stateDir: string,
134
+ archiveDir: string,
135
+ ): Promise<void> {
136
+ let baseStat;
137
+ try {
138
+ baseStat = await lstat(baseDir);
139
+ } catch (error) {
140
+ if (isErrnoCode(error, "ENOENT")) return;
141
+ throw error;
142
+ }
143
+ if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {
144
+ throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${baseDir}.`);
145
+ }
146
+ const memoryRoot = await realpath(baseDir);
147
+ const roots = [
148
+ entitiesDir,
149
+ stateDir,
150
+ ...RECALL_FALLBACK_DIRS.map((directory) => path.join(baseDir, directory)),
151
+ path.join(baseDir, "cold"),
152
+ archiveDir,
153
+ ];
154
+ for (const root of roots) {
155
+ let rootStat;
156
+ try {
157
+ rootStat = await lstat(root);
158
+ } catch (error) {
159
+ if (isErrnoCode(error, "ENOENT")) continue;
160
+ throw error;
161
+ }
162
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
163
+ throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${root}.`);
164
+ }
165
+ assertPathInsideRoot(memoryRoot, await realpath(root), root);
166
+ }
167
+ }
168
+
169
+ function statePath(deps: EntityCanonicalIdMigrationDependencies): string {
170
+ return path.join(deps.stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
171
+ }
172
+
173
+ async function assertSafeMigrationStateFile(
174
+ deps: EntityCanonicalIdMigrationDependencies,
175
+ ): Promise<void> {
176
+ const migrationStatePath = statePath(deps);
177
+ let fileStat;
178
+ try {
179
+ fileStat = await lstat(migrationStatePath);
180
+ } catch (error) {
181
+ if (isErrnoCode(error, "ENOENT")) return;
182
+ throw error;
183
+ }
184
+ if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
185
+ throw new Error(
186
+ `Refusing entity canonical-id migration through unsafe migration state file: ${migrationStatePath}.`,
187
+ );
188
+ }
189
+ const [stateRoot, stateFile] = await Promise.all([
190
+ realpath(deps.stateDir),
191
+ realpath(migrationStatePath),
192
+ ]);
193
+ assertPathInsideRoot(stateRoot, stateFile, migrationStatePath);
194
+ }
195
+ async function assertSafeTombstoneFile(
196
+ deps: EntityCanonicalIdMigrationDependencies,
197
+ tombstonePath: string,
198
+ ): Promise<boolean> {
199
+ let fileStat;
200
+ try {
201
+ fileStat = await lstat(tombstonePath);
202
+ } catch (error) {
203
+ if (isErrnoCode(error, "ENOENT")) return false;
204
+ throw error;
205
+ }
206
+ if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
207
+ throw new Error(
208
+ `Refusing entity canonical-id migration through unsafe tombstone ledger: ${tombstonePath}.`,
209
+ );
210
+ }
211
+ const [stateRoot, tombstoneFile] = await Promise.all([
212
+ realpath(deps.stateDir),
213
+ realpath(tombstonePath),
214
+ ]);
215
+ assertPathInsideRoot(stateRoot, tombstoneFile, tombstonePath);
216
+ return true;
217
+ }
218
+
219
+ function lockPath(deps: EntityCanonicalIdMigrationDependencies): string {
220
+ return path.join(deps.stateDir, "entity-canonical-id-migration.lock");
221
+ }
222
+
223
+ export async function withEntityCanonicalMutationLock<T>(
224
+ stateDir: string,
225
+ task: (refreshLock: () => Promise<boolean>) => Promise<T>,
226
+ ): Promise<T> {
227
+ return withHeldFileLock(
228
+ path.join(stateDir, "entity-canonical-id-mutation.lock"),
229
+ {
230
+ staleMs: ENTITY_CANONICAL_ID_MUTATION_LOCK_STALE_MS,
231
+ maxWaitMs: ENTITY_CANONICAL_ID_MUTATION_LOCK_MAX_WAIT_MS,
232
+ },
233
+ async (acquired, lock) => {
234
+ if (!acquired) throw new Error("Timed out waiting for entity mutation lock.");
235
+ return task(() => lock.refresh());
236
+ },
237
+ );
238
+ }
239
+
240
+ async function readState(
241
+ deps: EntityCanonicalIdMigrationDependencies
242
+ ): Promise<EntityCanonicalIdMigrationState | null> {
243
+ await assertSafeMigrationStateFile(deps);
244
+ let raw: string;
245
+ try {
246
+ raw = await readFile(statePath(deps), "utf-8");
247
+ } catch (error) {
248
+ if (isErrnoCode(error, "ENOENT")) return null;
249
+ throw error;
250
+ }
251
+
252
+ let parsed: unknown;
253
+ try {
254
+ parsed = JSON.parse(raw);
255
+ } catch {
256
+ throw new Error("Invalid entity canonical-id migration state.");
257
+ }
258
+ const result = EntityCanonicalIdMigrationStateSchema.safeParse(parsed);
259
+ if (!result.success) throw new Error("Invalid entity canonical-id migration state.");
260
+ return result.data;
261
+ }
262
+
263
+ async function writeState(
264
+ deps: EntityCanonicalIdMigrationDependencies,
265
+ state: EntityCanonicalIdMigrationState
266
+ ): Promise<void> {
267
+ const destination = statePath(deps);
268
+ const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;
269
+ try {
270
+ await writeFile(temporary, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
271
+ await rename(temporary, destination);
272
+ } finally {
273
+ await unlink(temporary).catch(() => undefined);
274
+ }
275
+ }
276
+
277
+ async function fileExists(filePath: string): Promise<boolean> {
278
+ try {
279
+ await access(filePath);
280
+ return true;
281
+ } catch (error) {
282
+ if (isErrnoCode(error, "ENOENT")) return false;
283
+ throw error;
284
+ }
285
+ }
286
+
287
+ async function sameFileIdentity(leftPath: string, rightPath: string): Promise<boolean> {
288
+ try {
289
+ const [left, right] = await Promise.all([statFile(leftPath), statFile(rightPath)]);
290
+ return left.dev === right.dev && left.ino === right.ino;
291
+ } catch (error) {
292
+ if (isErrnoCode(error, "ENOENT")) return false;
293
+ throw error;
294
+ }
295
+ }
296
+
297
+ function assertSafeEntityId(entityId: string): void {
298
+ if (/[\/\\\0]/.test(entityId)) {
299
+ throw new Error(`Refusing entity canonical-id migration through unsafe entity id: ${entityId}.`);
300
+ }
301
+ }
302
+
303
+ async function assertNotSymlink(filePath: string, label: string): Promise<void> {
304
+ let stat;
305
+ try {
306
+ stat = await lstat(filePath);
307
+ } catch (error) {
308
+ if (isErrnoCode(error, "ENOENT")) return;
309
+ throw error;
310
+ }
311
+ if (stat.isSymbolicLink()) {
312
+ throw new Error(`Refusing entity canonical-id migration through symlink: ${label}.`);
313
+ }
314
+ }
315
+ async function assertNotMemorySymlink(filePath: string): Promise<void> {
316
+ let stat;
317
+ try {
318
+ stat = await lstat(filePath);
319
+ } catch (error) {
320
+ if (isErrnoCode(error, "ENOENT")) return;
321
+ throw error;
322
+ }
323
+ if (stat.isSymbolicLink()) {
324
+ throw new Error(`Refusing entity canonical-id migration through symlinked memory: ${filePath}.`);
325
+ }
326
+ }
327
+ async function assertEntityContentUnchanged(
328
+ deps: EntityCanonicalIdMigrationDependencies,
329
+ filePath: string,
330
+ expectedContent: string,
331
+ legacyId: string,
332
+ ): Promise<void> {
333
+ const latestContent = await deps.readStorageSecureFile(filePath);
334
+ if (latestContent !== expectedContent) {
335
+ throw new Error(`Legacy entity id ${legacyId} changed while migration was running; retry migration.`);
336
+ }
337
+ }
338
+
339
+ async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
340
+ const rootStat = await lstat(entitiesDir);
341
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
342
+ throw new Error("Refusing entity canonical-id migration through a symlinked or non-directory entities root.");
343
+ }
344
+ const entries = await readdir(entitiesDir);
345
+ for (const entry of entries) {
346
+ await assertNotSymlink(path.join(entitiesDir, entry), entry);
347
+ }
348
+ return entries;
349
+ }
350
+
351
+ async function discoverMappings(
352
+ deps: EntityCanonicalIdMigrationDependencies,
353
+ knownMappings: Readonly<Record<string, string>> = {},
354
+ ): Promise<Record<string, string>> {
355
+ const mappings: Record<string, string> = {};
356
+ const canonicalOwners = new Map<string, string>();
357
+ const entityEntries = await readSafeEntityEntries(deps.entitiesDir);
358
+ for (const entry of entityEntries) {
359
+ if (!entry.endsWith(".md")) continue;
360
+ const legacyId = entry.slice(0, -".md".length);
361
+ assertSafeEntityId(legacyId);
362
+ const legacyPath = deps.resolveEntityFilePath(legacyId);
363
+ if (legacyPath === null) continue;
364
+ const entityContent = await deps.readStorageSecureFile(legacyPath);
365
+ if (
366
+ !/^#[ \t]+[^\r\n \t]/m.test(entityContent)
367
+ || !/^\*\*Type:\*\*[ \t]*[^\r\n \t]/m.test(entityContent)
368
+ ) {
369
+ throw new Error(`Cannot migrate malformed entity file ${entry}: missing name heading or type.`);
370
+ }
371
+ const entity = deps.parseEntityFile(entityContent);
372
+ if (!entity.name.trim() || !entity.type.trim()) {
373
+ throw new Error(`Cannot migrate malformed entity file ${entry}: missing name or type.`);
374
+ }
375
+ const canonicalId = deps.normalizeEntityName(entity.name, entity.type);
376
+ assertSafeEntityId(canonicalId);
377
+ if (legacyId === canonicalId) continue;
378
+ const canonicalPath = deps.resolveEntityFilePath(canonicalId);
379
+ if (canonicalPath === null) continue;
380
+ const canonicalExists = await fileExists(canonicalPath);
381
+ if (
382
+ canonicalExists
383
+ && await sameFileIdentity(legacyPath, canonicalPath)
384
+ && knownMappings[legacyId] === canonicalId
385
+ ) continue;
386
+ if (canonicalExists) {
387
+ const canonicalContent = await deps.readStorageSecureFile(canonicalPath);
388
+ if (entityContent !== canonicalContent) {
389
+ throw new Error(`Cannot migrate legacy entity id ${legacyId}: ${canonicalId} already exists.`);
390
+ }
391
+ }
392
+ const previousOwner = canonicalOwners.get(canonicalId);
393
+ if (previousOwner !== undefined && previousOwner !== legacyId) {
394
+ throw new Error(
395
+ `Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`,
396
+ );
397
+ }
398
+ canonicalOwners.set(canonicalId, legacyId);
399
+ mappings[legacyId] = canonicalId;
400
+ }
401
+ return mappings;
402
+ }
403
+
404
+ async function rewriteReferences(
405
+ deps: EntityCanonicalIdMigrationDependencies,
406
+ mappings: Readonly<Record<string, string>>,
407
+ refreshLock: () => Promise<boolean>
408
+ ): Promise<{ rewroteColdMemory: boolean }> {
409
+ let rewroteColdMemory = false;
410
+ for (let pass = 0; pass < MEMORY_REWRITE_MAX_PASSES; pass += 1) {
411
+ deps.invalidateAllMemoriesCache();
412
+ deps.invalidateColdMemoriesCache();
413
+ const [hotMemories, coldMemories, archivedMemories] = await Promise.all([
414
+ deps.readAllMemories(),
415
+ deps.readAllColdMemories(),
416
+ deps.readArchivedMemories(),
417
+ ]);
418
+ const seenPaths = new Set<string>();
419
+ let rewroteAny = false;
420
+ for (const memory of [...hotMemories, ...coldMemories, ...archivedMemories]) {
421
+ const memoryPath = path.resolve(memory.path);
422
+ if (seenPaths.has(memoryPath)) continue;
423
+ seenPaths.add(memoryPath);
424
+ const previousEntityRef = memory.frontmatter.entityRef;
425
+ const canonicalId = previousEntityRef ? mappings[previousEntityRef] : undefined;
426
+ if (!previousEntityRef || !canonicalId) continue;
427
+ await assertNotMemorySymlink(memory.path);
428
+ const observedMemoryFingerprint = await fingerprintPath(memory.path);
429
+ if (observedMemoryFingerprint === "missing") continue;
430
+ if (!(await refreshLock())) throw new Error("Lost entity canonical-id migration lock.");
431
+ const latestMemory = await deps.readMemoryByPath(memory.path);
432
+ if (!latestMemory) continue;
433
+ const latestMemoryFingerprint = await fingerprintPath(latestMemory.path);
434
+ if (latestMemoryFingerprint !== observedMemoryFingerprint) continue;
435
+ const latestPreviousEntityRef = latestMemory.frontmatter.entityRef;
436
+ const latestCanonicalId = latestPreviousEntityRef ? mappings[latestPreviousEntityRef] : undefined;
437
+ if (!latestPreviousEntityRef || !latestCanonicalId) continue;
438
+ const memoryEncrypted = await deps.isEncryptedStorageFile(latestMemory.path);
439
+ if ((await fingerprintPath(latestMemory.path)) !== latestMemoryFingerprint) continue;
440
+ await deps.snapshotBeforeWrite(latestMemory.path, "write");
441
+ if ((await fingerprintPath(latestMemory.path)) !== latestMemoryFingerprint) continue;
442
+ await deps.writeStorageSecureFile(
443
+ latestMemory.path,
444
+ deps.serializeMemoryWithEntityRef(latestMemory, latestCanonicalId),
445
+ memoryEncrypted,
446
+ );
447
+ await deps.rewriteProjectedMemoryEntityReference(
448
+ latestMemory.frontmatter.id,
449
+ latestPreviousEntityRef,
450
+ latestCanonicalId,
451
+ );
452
+ rewroteAny = true;
453
+ rewroteColdMemory ||= latestMemory.path.includes(`${path.sep}cold${path.sep}`);
454
+ }
455
+ if (!rewroteAny) return { rewroteColdMemory };
456
+ }
457
+ throw new Error("Memory references changed while entity canonical-id migration was running; retry migration.");
458
+ }
459
+
460
+ function rewriteTombstoneLine(
461
+ line: string,
462
+ mappings: Readonly<Record<string, string>>,
463
+ normalizedMappings: ReadonlyMap<string, string>,
464
+ ): string {
465
+ let parsed: unknown;
466
+ try {
467
+ parsed = JSON.parse(line);
468
+ } catch {
469
+ return line;
470
+ }
471
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return line;
472
+
473
+ const tombstone = parsed as Record<string, unknown>;
474
+ let changed = false;
475
+ if (typeof tombstone.entityRef === "string") {
476
+ const canonicalId = mappings[tombstone.entityRef];
477
+ if (canonicalId) {
478
+ tombstone.entityRef = canonicalId;
479
+ changed = true;
480
+ }
481
+ }
482
+ if (typeof tombstone.supersessionKey === "string") {
483
+ const separator = tombstone.supersessionKey.indexOf("::");
484
+ if (separator < 0) return changed ? JSON.stringify(tombstone) : line;
485
+ const normalizedLegacyId = normalizeSupersessionKey(tombstone.supersessionKey.slice(0, separator));
486
+ const canonicalId = normalizedMappings.get(normalizedLegacyId);
487
+ if (canonicalId) {
488
+ const canonicalKey = computeSupersessionKey(canonicalId, tombstone.supersessionKey.slice(separator + 2));
489
+ if (canonicalKey) {
490
+ tombstone.supersessionKey = canonicalKey;
491
+ changed = true;
492
+ }
493
+ }
494
+ }
495
+ return changed ? JSON.stringify(tombstone) : line;
496
+ }
497
+
498
+ async function rewriteTombstoneReferences(
499
+ deps: EntityCanonicalIdMigrationDependencies,
500
+ mappings: Readonly<Record<string, string>>,
501
+ refreshLock: () => Promise<boolean>
502
+ ): Promise<void> {
503
+ const tombstonePath = path.join(deps.stateDir, "tombstones.jsonl");
504
+ const normalizedMappings = new Map<string, string>();
505
+ for (const [legacyId, canonicalId] of Object.entries(mappings)) {
506
+ const normalizedLegacyId = normalizeSupersessionKey(legacyId);
507
+ if (normalizedLegacyId && !normalizedMappings.has(normalizedLegacyId)) {
508
+ normalizedMappings.set(normalizedLegacyId, canonicalId);
509
+ }
510
+ }
511
+
512
+ await withHeldFileLock(
513
+ path.join(deps.stateDir, "tombstones.lock"),
514
+ {
515
+ staleMs: TOMBSTONE_LOCK_STALE_MS,
516
+ maxWaitMs: TOMBSTONE_LOCK_MAX_WAIT_MS,
517
+ },
518
+ async (acquired, tombstoneLock) => {
519
+ if (!acquired) throw new Error("Timed out waiting for active tombstone migration.");
520
+ if (!(await assertSafeTombstoneFile(deps, tombstonePath))) return;
521
+ const tombstoneEncrypted = await deps.isEncryptedStorageFile(tombstonePath);
522
+ const original = await deps.readStorageSecureFile(tombstonePath);
523
+ let changed = false;
524
+ const rewritten = original
525
+ .split("\n")
526
+ .map((line) => {
527
+ const next = rewriteTombstoneLine(line, mappings, normalizedMappings);
528
+ changed ||= next !== line;
529
+ return next;
530
+ })
531
+ .join("\n");
532
+ if (!changed) return;
533
+ if (!(await refreshLock())) throw new Error("Lost entity canonical-id migration lock.");
534
+ if (!(await tombstoneLock.refresh())) throw new Error("Lost tombstone migration lock.");
535
+ await deps.snapshotBeforeWrite(tombstonePath, "write");
536
+ await deps.writeStorageSecureFile(tombstonePath, rewritten, tombstoneEncrypted);
537
+ },
538
+ );
539
+ }
540
+
541
+ async function rewriteRelationshipTargets(
542
+ deps: EntityCanonicalIdMigrationDependencies,
543
+ mappings: Readonly<Record<string, string>>,
544
+ refreshLock: () => Promise<boolean>
545
+ ): Promise<boolean> {
546
+ let rewritten = false;
547
+ const entries = await readSafeEntityEntries(deps.entitiesDir);
548
+ for (const entry of entries) {
549
+ if (!entry.endsWith(".md")) continue;
550
+ const entityPath = deps.resolveEntityFilePath(entry.slice(0, -".md".length));
551
+ if (entityPath === null) continue;
552
+ const observedEntityFingerprint = await fingerprintPath(entityPath);
553
+ if (observedEntityFingerprint === "missing") continue;
554
+ const entity = deps.parseEntityFile(await deps.readStorageSecureFile(entityPath));
555
+ const relationships = entity.relationships.map((relationship) => {
556
+ const target = mappings[relationship.target];
557
+ return target ? { ...relationship, target } : relationship;
558
+ });
559
+ if (relationships.every((relationship, index) => relationship === entity.relationships[index])) continue;
560
+ const rewroteEntity = await withEntityCanonicalMutationLock(
561
+ deps.stateDir,
562
+ async (refreshEntityLock) => {
563
+ if (!(await refreshLock())) throw new Error("Lost entity canonical-id migration lock.");
564
+ const latestEntity = deps.parseEntityFile(await deps.readStorageSecureFile(entityPath));
565
+ if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;
566
+ const latestRelationships = latestEntity.relationships.map((relationship) => {
567
+ const target = mappings[relationship.target];
568
+ return target ? { ...relationship, target } : relationship;
569
+ });
570
+ if (latestRelationships.every((relationship, index) => relationship === latestEntity.relationships[index])) {
571
+ return false;
572
+ }
573
+ const entityEncrypted = await deps.isEncryptedStorageFile(entityPath);
574
+ if (!(await refreshEntityLock())) throw new Error("Lost entity mutation lock.");
575
+ if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;
576
+ await deps.snapshotBeforeWrite(entityPath, "write");
577
+ if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;
578
+ await deps.writeStorageSecureFile(
579
+ entityPath,
580
+ deps.serializeEntityFile({ ...latestEntity, relationships: latestRelationships }),
581
+ entityEncrypted,
582
+ );
583
+ return true;
584
+ },
585
+ );
586
+ rewritten ||= rewroteEntity;
587
+ }
588
+ return rewritten;
589
+ }
590
+
591
+ async function rewriteKnownReferences(
592
+ deps: EntityCanonicalIdMigrationDependencies,
593
+ mappings: Readonly<Record<string, string>>,
594
+ refreshLock: () => Promise<boolean>,
595
+ ): Promise<boolean> {
596
+ await rewriteRelationshipTargets(deps, mappings, refreshLock);
597
+ let rewroteColdMemory = (await rewriteReferences(deps, mappings, refreshLock)).rewroteColdMemory;
598
+ await rewriteTombstoneReferences(deps, mappings, refreshLock);
599
+ await deps.rewriteProjectedEntityReferences(mappings);
600
+ const finalMemoryPass = await rewriteReferences(deps, mappings, refreshLock);
601
+ rewroteColdMemory ||= finalMemoryPass.rewroteColdMemory;
602
+ return rewroteColdMemory;
603
+ }
604
+
605
+ function wouldCreateMappingCycle(
606
+ mappings: Readonly<Record<string, string>>,
607
+ legacyId: string,
608
+ canonicalId: string,
609
+ ): boolean {
610
+ let current = canonicalId;
611
+ const seen = new Set<string>();
612
+ while (true) {
613
+ if (current === legacyId) return true;
614
+ if (seen.has(current)) return false;
615
+ seen.add(current);
616
+ const next = mappings[current];
617
+ if (!next || next === current) return false;
618
+ current = next;
619
+ }
620
+ }
621
+
622
+ function rejectMappingCycles(
623
+ existing: Readonly<Record<string, string>>,
624
+ discovered: Readonly<Record<string, string>>,
625
+ ): Record<string, string> {
626
+ const accepted: Record<string, string> = {};
627
+ const merged = { ...existing };
628
+ for (const [legacyId, canonicalId] of Object.entries(discovered)) {
629
+ if (
630
+ merged[legacyId] === undefined &&
631
+ wouldCreateMappingCycle(merged, legacyId, canonicalId)
632
+ ) continue;
633
+ accepted[legacyId] = canonicalId;
634
+ merged[legacyId] = canonicalId;
635
+ }
636
+ return accepted;
637
+ }
638
+
639
+ function mergeDiscoveredMappings(
640
+ existing: Readonly<Record<string, string>>,
641
+ discovered: Readonly<Record<string, string>>,
642
+ ): Record<string, string> {
643
+ const merged = { ...existing };
644
+ const canonicalOwners = new Map<string, string>();
645
+ for (const [legacyId, canonicalId] of Object.entries(discovered)) {
646
+ const previousMapping = merged[legacyId];
647
+ if (previousMapping !== undefined && previousMapping !== canonicalId) {
648
+ throw new Error(`Legacy entity id ${legacyId} changed canonical target during migration.`);
649
+ }
650
+ if (previousMapping === undefined && wouldCreateMappingCycle(merged, legacyId, canonicalId)) continue;
651
+ const previousOwner = canonicalOwners.get(canonicalId);
652
+ if (previousOwner !== undefined && previousOwner !== legacyId) {
653
+ throw new Error(
654
+ `Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`,
655
+ );
656
+ }
657
+ merged[legacyId] = canonicalId;
658
+ canonicalOwners.set(canonicalId, legacyId);
659
+ }
660
+ return merged;
661
+ }
662
+ function collapseMappings(mappings: Readonly<Record<string, string>>): Record<string, string> {
663
+ const collapsed: Record<string, string> = {};
664
+ for (const legacyId of Object.keys(mappings)) {
665
+ let current = legacyId;
666
+ const seen = new Set<string>();
667
+ while (true) {
668
+ const next = mappings[current];
669
+ if (!next || next === current) break;
670
+ if (seen.has(current)) {
671
+ throw new Error(`Entity canonical-id migration mapping cycle includes ${legacyId}.`);
672
+ }
673
+ seen.add(current);
674
+ current = next;
675
+ }
676
+ if (current !== legacyId) collapsed[legacyId] = current;
677
+ }
678
+ return collapsed;
679
+ }
680
+
681
+ type EntityFileMigrationResult = {
682
+ deferred: boolean;
683
+ progressed: boolean;
684
+ };
685
+
686
+ async function migrateEntityFilePair(
687
+ deps: EntityCanonicalIdMigrationDependencies,
688
+ legacyId: string,
689
+ canonicalId: string,
690
+ refreshMigrationLock: () => Promise<boolean>,
691
+ ): Promise<EntityFileMigrationResult> {
692
+ return withEntityCanonicalMutationLock(deps.stateDir, async (refreshEntityLock) => {
693
+ assertSafeEntityId(legacyId);
694
+ assertSafeEntityId(canonicalId);
695
+ const legacyPath = deps.resolveEntityFilePath(legacyId);
696
+ const canonicalPath = deps.resolveEntityFilePath(canonicalId);
697
+ if (legacyPath === null || canonicalPath === null) {
698
+ throw new Error("Invalid entity canonical-id migration path.");
699
+ }
700
+ await assertNotSymlink(legacyPath, legacyId);
701
+ await assertNotSymlink(canonicalPath, canonicalId);
702
+ const legacyExists = await fileExists(legacyPath);
703
+ const canonicalExists = await fileExists(canonicalPath);
704
+ if (!legacyExists && !canonicalExists) return { deferred: true, progressed: false };
705
+ if (legacyExists && canonicalExists) {
706
+ if (await sameFileIdentity(legacyPath, canonicalPath)) return { deferred: false, progressed: false };
707
+ const [legacyContent, canonicalContent, legacyEncrypted, canonicalEncrypted] = await Promise.all([
708
+ deps.readStorageSecureFile(legacyPath),
709
+ deps.readStorageSecureFile(canonicalPath),
710
+ deps.isEncryptedStorageFile(legacyPath),
711
+ deps.isEncryptedStorageFile(canonicalPath),
712
+ ]);
713
+ if (legacyContent !== canonicalContent) {
714
+ throw new Error(`Cannot migrate legacy entity id ${legacyId}: ${canonicalId} already exists.`);
715
+ }
716
+ if (legacyEncrypted && !canonicalEncrypted) {
717
+ if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {
718
+ throw new Error("Lost entity canonical-id migration lock.");
719
+ }
720
+ await deps.snapshotBeforeWrite(canonicalPath, "write");
721
+ await deps.writeStorageSecureFile(canonicalPath, legacyContent, true);
722
+ if ((await deps.readStorageSecureFile(canonicalPath)) !== legacyContent) {
723
+ throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);
724
+ }
725
+ }
726
+ if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {
727
+ throw new Error("Lost entity canonical-id migration lock.");
728
+ }
729
+ await deps.snapshotBeforeWrite(legacyPath, "write");
730
+ await assertNotSymlink(legacyPath, legacyId);
731
+ await assertEntityContentUnchanged(deps, legacyPath, legacyContent, legacyId);
732
+ await unlink(legacyPath);
733
+ return { deferred: false, progressed: true };
734
+ }
735
+ if (!legacyExists) return { deferred: false, progressed: false };
736
+ if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {
737
+ throw new Error("Lost entity canonical-id migration lock.");
738
+ }
739
+ const entityEncrypted = await deps.isEncryptedStorageFile(legacyPath);
740
+ const entityContent = await deps.readStorageSecureFile(legacyPath);
741
+ await assertNotSymlink(legacyPath, legacyId);
742
+ await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);
743
+ let canonicalFingerprintAfterWrite: string | null = null;
744
+ try {
745
+ await deps.writeStorageSecureFile(canonicalPath, entityContent, entityEncrypted);
746
+ canonicalFingerprintAfterWrite = await fingerprintPath(canonicalPath);
747
+ if ((await deps.readStorageSecureFile(canonicalPath)) !== entityContent) {
748
+ throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);
749
+ }
750
+ if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {
751
+ throw new Error("Lost entity canonical-id migration lock.");
752
+ }
753
+ await deps.snapshotBeforeWrite(legacyPath, "write");
754
+ await assertNotSymlink(legacyPath, legacyId);
755
+ await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);
756
+ await unlink(legacyPath);
757
+ } catch (error) {
758
+ if (!canonicalExists && canonicalFingerprintAfterWrite !== null) {
759
+ try {
760
+ if ((await fingerprintPath(canonicalPath)) === canonicalFingerprintAfterWrite) {
761
+ await assertNotSymlink(canonicalPath, canonicalId);
762
+ await unlink(canonicalPath);
763
+ }
764
+ } catch {
765
+ // Preserve the migration error when cleanup races another writer.
766
+ }
767
+ }
768
+ throw error;
769
+ }
770
+ return { deferred: false, progressed: true };
771
+ });
772
+ }
773
+
774
+ export async function migrateLegacyEntityCanonicalIds(
775
+ deps: EntityCanonicalIdMigrationDependencies,
776
+ ): Promise<string | undefined> {
777
+ return withHeldFileLock(
778
+ lockPath(deps),
779
+ {
780
+ staleMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS,
781
+ maxWaitMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS,
782
+ },
783
+ async (acquired, lock) => {
784
+ if (!acquired) throw new Error("Timed out waiting for active entity canonical-id migration.");
785
+ await deps.validateMemoryScanRoots?.();
786
+ await readSafeEntityEntries(deps.entitiesDir);
787
+ let state = await readState(deps);
788
+ if (!state) {
789
+ state = {
790
+ version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,
791
+ complete: false,
792
+ mappings: {},
793
+ };
794
+ }
795
+ const previousMappings = state.mappings;
796
+ const collapsedMappings = collapseMappings(previousMappings);
797
+ const mappingsChanged =
798
+ Object.keys(collapsedMappings).length !== Object.keys(previousMappings).length
799
+ || Object.entries(collapsedMappings).some(([legacyId, canonicalId]) =>
800
+ previousMappings[legacyId] !== canonicalId
801
+ );
802
+ state = { ...state, mappings: collapsedMappings };
803
+ for (let migrationPass = 0; migrationPass < ENTITY_MAPPING_RESCAN_MAX_PASSES; migrationPass += 1) {
804
+ if (!(await lock.refresh())) throw new Error("Lost entity canonical-id migration lock.");
805
+ const discoveredBefore = rejectMappingCycles(
806
+ state.mappings,
807
+ await discoverMappings(deps, state.mappings),
808
+ );
809
+ const mergedBefore = collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredBefore));
810
+ if (state.complete && Object.keys(discoveredBefore).length === 0 && !mappingsChanged) {
811
+ if (Object.keys(state.mappings).length === 0) return deps.readMigrationFingerprint?.();
812
+ const rewroteColdMemory = await rewriteKnownReferences(
813
+ deps,
814
+ state.mappings,
815
+ () => lock.refresh(),
816
+ );
817
+ deps.invalidateKnowledgeIndexCache();
818
+ deps.invalidateAllMemoriesCache();
819
+ if (rewroteColdMemory) deps.invalidateColdMemoriesCache();
820
+ deps.bumpMemoryStatusVersion();
821
+ return deps.readMigrationFingerprint?.();
822
+ }
823
+ if (
824
+ state.complete
825
+ || Object.keys(discoveredBefore).length > 0
826
+ || Object.keys(mergedBefore).length !== Object.keys(state.mappings).length
827
+ ) {
828
+ state = { ...state, complete: false, mappings: mergedBefore };
829
+ await writeState(deps, state);
830
+ }
831
+ if (Object.keys(state.mappings).length === 0) {
832
+ if (!(await lock.refresh())) throw new Error("Lost entity canonical-id migration lock.");
833
+ const discoveredAfter = rejectMappingCycles(
834
+ state.mappings,
835
+ await discoverMappings(deps, state.mappings),
836
+ );
837
+ if (Object.keys(discoveredAfter).length === 0) {
838
+ await writeState(deps, { ...state, complete: true });
839
+ return deps.readMigrationFingerprint?.();
840
+ }
841
+ state = {
842
+ ...state,
843
+ complete: false,
844
+ mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter)),
845
+ };
846
+ await writeState(deps, state);
847
+ continue;
848
+ }
849
+ let pendingMappings = Object.entries(state.mappings);
850
+ while (pendingMappings.length > 0) {
851
+ const deferredMappings: Array<[string, string]> = [];
852
+ let progressed = false;
853
+ for (const [legacyId, canonicalId] of pendingMappings) {
854
+ const result = await migrateEntityFilePair(
855
+ deps,
856
+ legacyId,
857
+ canonicalId,
858
+ () => lock.refresh(),
859
+ );
860
+ if (result.deferred) {
861
+ deferredMappings.push([legacyId, canonicalId]);
862
+ }
863
+ progressed ||= result.progressed;
864
+ }
865
+ if (deferredMappings.length === 0) break;
866
+ if (!progressed) {
867
+ const [legacyId, canonicalId] = deferredMappings[0]!;
868
+ throw new Error(
869
+ `Cannot migrate legacy entity id ${legacyId}: both files are missing and no intermediate mapping can advance migration.`,
870
+ );
871
+ }
872
+ pendingMappings = deferredMappings;
873
+ }
874
+
875
+ const rewroteColdMemory = await rewriteKnownReferences(
876
+ deps,
877
+ state.mappings,
878
+ () => lock.refresh(),
879
+ );
880
+ deps.invalidateKnowledgeIndexCache();
881
+ deps.invalidateAllMemoriesCache();
882
+ if (rewroteColdMemory) deps.invalidateColdMemoriesCache();
883
+ deps.bumpMemoryStatusVersion();
884
+
885
+ if (!(await lock.refresh())) throw new Error("Lost entity canonical-id migration lock.");
886
+ const discoveredAfter = rejectMappingCycles(
887
+ state.mappings,
888
+ await discoverMappings(deps, state.mappings),
889
+ );
890
+ if (Object.keys(discoveredAfter).length === 0) {
891
+ await writeState(deps, { ...state, complete: true });
892
+ return deps.readMigrationFingerprint?.();
893
+ }
894
+ state = {
895
+ ...state,
896
+ complete: false,
897
+ mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter)),
898
+ };
899
+ await writeState(deps, state);
900
+ }
901
+ throw new Error("Entity canonical-id migration mappings changed while migration was running; retry migration.");
902
+ }
903
+ );
904
+ }