@remnic/core 10.0.0 → 10.0.2

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 (295) hide show
  1. package/dist/access-admin-ops-surface.js +46 -43
  2. package/dist/access-authorization-probe.js +47 -44
  3. package/dist/access-boundary.js +46 -43
  4. package/dist/access-cli.js +111 -108
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +46 -43
  7. package/dist/access-http-briefing.js +47 -44
  8. package/dist/access-http-query.js +48 -45
  9. package/dist/access-http.js +66 -63
  10. package/dist/access-identity-continuity-surface.js +46 -43
  11. package/dist/access-lcm-surface.js +46 -43
  12. package/dist/access-mcp.js +55 -52
  13. package/dist/access-namespace-preflight.js +46 -43
  14. package/dist/access-observe-write-category.js +9 -9
  15. package/dist/access-observe-write-surface.js +46 -43
  16. package/dist/access-offline-manifest.js +6 -6
  17. package/dist/access-operations-batch.js +47 -44
  18. package/dist/access-operations.js +54 -51
  19. package/dist/access-recall-concurrency.js +46 -43
  20. package/dist/access-recall-response.js +46 -43
  21. package/dist/access-recall-surface.js +46 -43
  22. package/dist/access-schema.js +8 -8
  23. package/dist/access-service.js +46 -43
  24. package/dist/access-surface-catalog.js +48 -45
  25. package/dist/access-surface-catalog.js.map +1 -1
  26. package/dist/active-recall.js +18 -16
  27. package/dist/active-recall.js.map +1 -1
  28. package/dist/adapters/index.js +4 -4
  29. package/dist/adapters/registry.js +2 -2
  30. package/dist/briefing.js +12 -10
  31. package/dist/buffer.js +7 -5
  32. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
  33. package/dist/causal-consolidation.js +13 -11
  34. package/dist/causal-consolidation.js.map +1 -1
  35. package/dist/{chunk-JWFTPRYD.js → chunk-2EZB2DL5.js} +2 -2
  36. package/dist/{chunk-J3ZJBX7I.js → chunk-2J3M2KV4.js} +2 -2
  37. package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
  38. package/dist/{chunk-SWXPR5SE.js → chunk-2SYRHFUG.js} +2 -2
  39. package/dist/{chunk-QKCGOECM.js → chunk-34G4U6HS.js} +2 -2
  40. package/dist/{chunk-QX3K3INR.js → chunk-36BUOTVN.js} +2 -2
  41. package/dist/{chunk-CNBGAFDY.js → chunk-3J54RR3A.js} +2 -2
  42. package/dist/{chunk-XLWMNDQR.js → chunk-4A2UMSNB.js} +7 -5
  43. package/dist/chunk-4A2UMSNB.js.map +1 -0
  44. package/dist/{chunk-OWV6LKNZ.js → chunk-4G2COUSD.js} +2 -2
  45. package/dist/{chunk-HWGFTTPS.js → chunk-5FLFPTDK.js} +2 -2
  46. package/dist/{chunk-WR66CTVU.js → chunk-7IGNDEUJ.js} +4 -4
  47. package/dist/chunk-7PZHH4KC.js +63 -0
  48. package/dist/chunk-7PZHH4KC.js.map +1 -0
  49. package/dist/{chunk-VO2BNAA2.js → chunk-A5N6ZEUW.js} +2 -2
  50. package/dist/{chunk-XQWKB6PY.js → chunk-AALVXJJ7.js} +2 -2
  51. package/dist/{chunk-KFHEZJQZ.js → chunk-BAREPCLY.js} +2 -2
  52. package/dist/{chunk-QAIVTLL2.js → chunk-BLHPADUX.js} +2 -2
  53. package/dist/{chunk-2TCNEJ36.js → chunk-BNMI3JBV.js} +3 -3
  54. package/dist/{chunk-QUNCLKHM.js → chunk-CAXMG4JI.js} +2 -2
  55. package/dist/{chunk-KV7LFOPF.js → chunk-CQBFEGLF.js} +2 -2
  56. package/dist/{chunk-KMN2JE4D.js → chunk-CVQWNACA.js} +2 -2
  57. package/dist/{chunk-VV2MVHCJ.js → chunk-DAVONFNK.js} +2 -2
  58. package/dist/{chunk-KYUVFCT2.js → chunk-DQQ4IM5L.js} +2 -2
  59. package/dist/{chunk-B454SHPU.js → chunk-E7FN5N5E.js} +2 -2
  60. package/dist/{chunk-CGBE3N7T.js → chunk-EB3RXN2L.js} +2 -2
  61. package/dist/{chunk-AWSP3WNM.js → chunk-EJJMDLVB.js} +2 -2
  62. package/dist/{chunk-A6WNTVA7.js → chunk-FNVSBUC3.js} +2 -2
  63. package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
  64. package/dist/{chunk-BKVCTTEN.js → chunk-GUD6FYQG.js} +2 -2
  65. package/dist/{chunk-2MMBJE53.js → chunk-H5YACCE3.js} +7 -7
  66. package/dist/{chunk-JC7QQ2DJ.js → chunk-HRZZJ4XB.js} +2 -2
  67. package/dist/{chunk-LHDSCWDK.js → chunk-I2VQ7WBJ.js} +16 -16
  68. package/dist/{chunk-B3OFQP5Y.js → chunk-IB63OVUB.js} +24 -249
  69. package/dist/chunk-IB63OVUB.js.map +1 -0
  70. package/dist/chunk-IM2GO42H.js +312 -0
  71. package/dist/chunk-IM2GO42H.js.map +1 -0
  72. package/dist/{chunk-PRNR47DI.js → chunk-IR5LZCE6.js} +2 -2
  73. package/dist/{chunk-YI6XIFQV.js → chunk-J5CUQRST.js} +2 -2
  74. package/dist/{chunk-6WRHNA4I.js → chunk-JFDKPW54.js} +35 -35
  75. package/dist/{chunk-VYIBRCVW.js → chunk-JOBE47EJ.js} +2 -2
  76. package/dist/{chunk-SFDBLCZV.js → chunk-K4TWTUEY.js} +41 -41
  77. package/dist/{chunk-UHESJ6TQ.js → chunk-KEGUXZNY.js} +3 -3
  78. package/dist/{chunk-UHESJ6TQ.js.map → chunk-KEGUXZNY.js.map} +1 -1
  79. package/dist/{chunk-T6RQNV7E.js → chunk-KHOINMCN.js} +2 -2
  80. package/dist/{chunk-VDX2J7OX.js → chunk-KQAFEZQX.js} +2 -2
  81. package/dist/{chunk-E4XLFYHV.js → chunk-LHOIYLKH.js} +2 -2
  82. package/dist/chunk-LHOIYLKH.js.map +1 -0
  83. package/dist/{chunk-PQKZLBCJ.js → chunk-LMZV3H4W.js} +127 -6
  84. package/dist/chunk-LMZV3H4W.js.map +1 -0
  85. package/dist/{chunk-MZOQ26LH.js → chunk-LQGX5TCI.js} +2 -2
  86. package/dist/{chunk-TRN56SPQ.js → chunk-M2OPC4BD.js} +2 -2
  87. package/dist/{chunk-LXJZUNLX.js → chunk-NBK24POV.js} +2 -2
  88. package/dist/{chunk-7X7A7KVN.js → chunk-NLYOOGZ7.js} +2 -2
  89. package/dist/{chunk-G5I5ZT6O.js → chunk-OSDAF7HH.js} +2 -2
  90. package/dist/{chunk-EZ3F4YIN.js → chunk-OV7EYM6M.js} +6 -6
  91. package/dist/{chunk-AU43D3IT.js → chunk-P57VR7AI.js} +4 -4
  92. package/dist/{chunk-NT2Q6AHC.js → chunk-PCO3P7WJ.js} +2 -2
  93. package/dist/{chunk-73R4HOOK.js → chunk-PT6DQK5T.js} +3 -3
  94. package/dist/{chunk-O43AAJFQ.js → chunk-PTGPU4DC.js} +2 -2
  95. package/dist/{chunk-STQC2FYQ.js → chunk-PWVBVIOP.js} +6 -6
  96. package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
  97. package/dist/{chunk-SIGDETMB.js → chunk-R4NGLTUS.js} +2 -2
  98. package/dist/{chunk-S2GR3T7W.js → chunk-RNYVAQX2.js} +2 -2
  99. package/dist/{chunk-PZ2QQULY.js → chunk-RU744TNX.js} +8 -8
  100. package/dist/{chunk-2OFDC3AK.js → chunk-RVQVWDSN.js} +2 -2
  101. package/dist/{chunk-ZLQ37WJX.js → chunk-SIGAGKNE.js} +8 -2
  102. package/dist/{chunk-ZLQ37WJX.js.map → chunk-SIGAGKNE.js.map} +1 -1
  103. package/dist/{chunk-OFGTRDWX.js → chunk-SIYZDT2V.js} +3 -3
  104. package/dist/{chunk-PQ7EB5AD.js → chunk-SQPC6MUM.js} +3 -3
  105. package/dist/{chunk-5R4YPRAR.js → chunk-TGF43K66.js} +18 -18
  106. package/dist/chunk-TPSLVZHS.js +102 -0
  107. package/dist/chunk-TPSLVZHS.js.map +1 -0
  108. package/dist/{chunk-ZRUC7FD3.js → chunk-U4SG4HKQ.js} +2 -2
  109. package/dist/{chunk-ATFFWIKL.js → chunk-U6HWKOGD.js} +2 -2
  110. package/dist/{chunk-IOMLGJHG.js → chunk-UK5EH7AI.js} +2 -2
  111. package/dist/{chunk-DB6XFMOF.js → chunk-VC4J4UTI.js} +5 -5
  112. package/dist/{chunk-T32YYUT2.js → chunk-VQTPEL7O.js} +2 -2
  113. package/dist/{chunk-7YQR6XHY.js → chunk-W6K6BC3H.js} +57 -57
  114. package/dist/{chunk-FYX2LXIX.js → chunk-WI37MWRZ.js} +2 -2
  115. package/dist/{chunk-7NBUE5VY.js → chunk-WPMDNCGB.js} +2 -2
  116. package/dist/{chunk-GZ2KV2RO.js → chunk-WWLAKRMX.js} +4 -4
  117. package/dist/{chunk-EWWK65CI.js → chunk-WY4IV62O.js} +2 -2
  118. package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
  119. package/dist/{chunk-GQ6ZF4LD.js → chunk-XG4XXI66.js} +2 -2
  120. package/dist/{chunk-Z6EZCN6Z.js → chunk-Y7427SL7.js} +10 -10
  121. package/dist/{chunk-TXTRHF3B.js → chunk-YAPTJQI7.js} +2 -2
  122. package/dist/{chunk-JRDL3FGM.js → chunk-YFET6A4I.js} +3 -3
  123. package/dist/{chunk-ISX7GVZE.js → chunk-YRULQW5J.js} +4 -4
  124. package/dist/{chunk-VDVHIRCK.js → chunk-YVHNBOK2.js} +2 -2
  125. package/dist/cli.js +85 -82
  126. package/dist/coding/export-okf-codegraph.js +11 -9
  127. package/dist/coding/export-okf-codegraph.js.map +1 -1
  128. package/dist/compounding/engine.js +12 -10
  129. package/dist/config-qmd.js +1 -1
  130. package/dist/config.js +18 -16
  131. package/dist/connectors/codex-materialize-runner.js +12 -10
  132. package/dist/connectors/index.js +12 -10
  133. package/dist/convergence-refresh.js +14 -12
  134. package/dist/conversation-index/backend.js +2 -2
  135. package/dist/corpus-watermark.js +14 -12
  136. package/dist/deep-recall-http-glue.js +9 -9
  137. package/dist/deep-recall.js +2 -2
  138. package/dist/entity-retrieval.js +12 -10
  139. package/dist/external-wiki-access.js +47 -44
  140. package/dist/extraction.js +16 -14
  141. package/dist/harmonic-retrieval.d.ts.map +1 -1
  142. package/dist/harmonic-retrieval.js +91 -1
  143. package/dist/index.js +136 -133
  144. package/dist/lcm/engine.js +4 -4
  145. package/dist/lcm/index.js +10 -10
  146. package/dist/location/index.js +14 -12
  147. package/dist/maintenance/memory-governance.js +12 -10
  148. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
  149. package/dist/maintenance/rebuild-memory-projection.d.ts.map +1 -1
  150. package/dist/maintenance/rebuild-memory-projection.js +13 -11
  151. package/dist/memory-browse-operations.js +47 -44
  152. package/dist/memory-browse.js +7 -5
  153. package/dist/memory-projection-batch.d.ts +20 -0
  154. package/dist/memory-projection-batch.d.ts.map +1 -0
  155. package/dist/memory-projection-batch.js +11 -0
  156. package/dist/memory-projection-batch.js.map +1 -0
  157. package/dist/namespaces/migrate.js +13 -11
  158. package/dist/namespaces/search.js +10 -8
  159. package/dist/namespaces/storage.js +12 -10
  160. package/dist/offline-sync-changeset-generations.js +3 -3
  161. package/dist/offline-sync-embedding-generation.js +2 -2
  162. package/dist/offline-sync-snapshot-stream.js +6 -6
  163. package/dist/offline-sync.js +5 -5
  164. package/dist/operator-doctor-corpus.js +15 -13
  165. package/dist/operator-toolkit.js +27 -25
  166. package/dist/orchestration/maintenance.js +20 -18
  167. package/dist/orchestrator.js +111 -108
  168. package/dist/qmd-daemon-session.d.ts +45 -0
  169. package/dist/qmd-daemon-session.d.ts.map +1 -0
  170. package/dist/qmd-daemon-session.js +12 -0
  171. package/dist/qmd-daemon-session.js.map +1 -0
  172. package/dist/qmd-mcp-cancel.d.ts +21 -0
  173. package/dist/qmd-mcp-cancel.d.ts.map +1 -0
  174. package/dist/qmd-mcp-cancel.js +11 -0
  175. package/dist/qmd-mcp-cancel.js.map +1 -0
  176. package/dist/qmd-preflight.d.ts +3 -1
  177. package/dist/qmd-preflight.d.ts.map +1 -1
  178. package/dist/qmd-preflight.js +3 -1
  179. package/dist/qmd.d.ts +5 -29
  180. package/dist/qmd.d.ts.map +1 -1
  181. package/dist/qmd.js +6 -2
  182. package/dist/recall-navigation-operations.js +47 -44
  183. package/dist/recall-navigation.js +46 -43
  184. package/dist/recall-state-view-admission.js +7 -5
  185. package/dist/recall-why-service.js +7 -5
  186. package/dist/resume-bundles.js +19 -17
  187. package/dist/search/factory.js +9 -7
  188. package/dist/search/index.js +13 -11
  189. package/dist/semantic-consolidation.js +13 -11
  190. package/dist/semantic-rule-promotion.js +12 -10
  191. package/dist/semantic-rule-verifier.js +12 -10
  192. package/dist/source-agent-qualifier.js +15 -13
  193. package/dist/standup/build.js +12 -10
  194. package/dist/storage.d.ts +1 -1
  195. package/dist/storage.d.ts.map +1 -1
  196. package/dist/storage.js +11 -9
  197. package/dist/support-passport/index.js +49 -46
  198. package/dist/transfer/backup.js +2 -2
  199. package/dist/transfer/capsule-export.js +2 -2
  200. package/dist/transfer/capsule-import.js +2 -2
  201. package/dist/transfer/export-okf.js +12 -10
  202. package/dist/verified-recall.js +12 -10
  203. package/dist/who-knows-cli.js +13 -11
  204. package/dist/who-knows.js +12 -10
  205. package/package.json +2 -2
  206. package/src/config-qmd.ts +2 -2
  207. package/src/harmonic-retrieval-index.test.ts +387 -0
  208. package/src/harmonic-retrieval.ts +181 -15
  209. package/src/maintenance/rebuild-memory-projection.ts +3 -0
  210. package/src/memory-projection-batch.ts +82 -0
  211. package/src/qmd-daemon-session.ts +369 -0
  212. package/src/qmd-mcp-cancel.test.ts +408 -0
  213. package/src/qmd-mcp-cancel.ts +130 -0
  214. package/src/qmd-preflight.ts +10 -1
  215. package/src/qmd-rank-compare.test.ts +100 -0
  216. package/src/qmd.test.ts +16 -1
  217. package/src/qmd.ts +18 -297
  218. package/src/storage.ts +1 -1
  219. package/dist/chunk-B3OFQP5Y.js.map +0 -1
  220. package/dist/chunk-E4XLFYHV.js.map +0 -1
  221. package/dist/chunk-PQKZLBCJ.js.map +0 -1
  222. package/dist/chunk-XLWMNDQR.js.map +0 -1
  223. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  224. /package/dist/{chunk-JWFTPRYD.js.map → chunk-2EZB2DL5.js.map} +0 -0
  225. /package/dist/{chunk-J3ZJBX7I.js.map → chunk-2J3M2KV4.js.map} +0 -0
  226. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
  227. /package/dist/{chunk-SWXPR5SE.js.map → chunk-2SYRHFUG.js.map} +0 -0
  228. /package/dist/{chunk-QKCGOECM.js.map → chunk-34G4U6HS.js.map} +0 -0
  229. /package/dist/{chunk-QX3K3INR.js.map → chunk-36BUOTVN.js.map} +0 -0
  230. /package/dist/{chunk-CNBGAFDY.js.map → chunk-3J54RR3A.js.map} +0 -0
  231. /package/dist/{chunk-OWV6LKNZ.js.map → chunk-4G2COUSD.js.map} +0 -0
  232. /package/dist/{chunk-HWGFTTPS.js.map → chunk-5FLFPTDK.js.map} +0 -0
  233. /package/dist/{chunk-WR66CTVU.js.map → chunk-7IGNDEUJ.js.map} +0 -0
  234. /package/dist/{chunk-VO2BNAA2.js.map → chunk-A5N6ZEUW.js.map} +0 -0
  235. /package/dist/{chunk-XQWKB6PY.js.map → chunk-AALVXJJ7.js.map} +0 -0
  236. /package/dist/{chunk-KFHEZJQZ.js.map → chunk-BAREPCLY.js.map} +0 -0
  237. /package/dist/{chunk-QAIVTLL2.js.map → chunk-BLHPADUX.js.map} +0 -0
  238. /package/dist/{chunk-2TCNEJ36.js.map → chunk-BNMI3JBV.js.map} +0 -0
  239. /package/dist/{chunk-QUNCLKHM.js.map → chunk-CAXMG4JI.js.map} +0 -0
  240. /package/dist/{chunk-KV7LFOPF.js.map → chunk-CQBFEGLF.js.map} +0 -0
  241. /package/dist/{chunk-KMN2JE4D.js.map → chunk-CVQWNACA.js.map} +0 -0
  242. /package/dist/{chunk-VV2MVHCJ.js.map → chunk-DAVONFNK.js.map} +0 -0
  243. /package/dist/{chunk-KYUVFCT2.js.map → chunk-DQQ4IM5L.js.map} +0 -0
  244. /package/dist/{chunk-B454SHPU.js.map → chunk-E7FN5N5E.js.map} +0 -0
  245. /package/dist/{chunk-CGBE3N7T.js.map → chunk-EB3RXN2L.js.map} +0 -0
  246. /package/dist/{chunk-AWSP3WNM.js.map → chunk-EJJMDLVB.js.map} +0 -0
  247. /package/dist/{chunk-A6WNTVA7.js.map → chunk-FNVSBUC3.js.map} +0 -0
  248. /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
  249. /package/dist/{chunk-BKVCTTEN.js.map → chunk-GUD6FYQG.js.map} +0 -0
  250. /package/dist/{chunk-2MMBJE53.js.map → chunk-H5YACCE3.js.map} +0 -0
  251. /package/dist/{chunk-JC7QQ2DJ.js.map → chunk-HRZZJ4XB.js.map} +0 -0
  252. /package/dist/{chunk-LHDSCWDK.js.map → chunk-I2VQ7WBJ.js.map} +0 -0
  253. /package/dist/{chunk-PRNR47DI.js.map → chunk-IR5LZCE6.js.map} +0 -0
  254. /package/dist/{chunk-YI6XIFQV.js.map → chunk-J5CUQRST.js.map} +0 -0
  255. /package/dist/{chunk-6WRHNA4I.js.map → chunk-JFDKPW54.js.map} +0 -0
  256. /package/dist/{chunk-VYIBRCVW.js.map → chunk-JOBE47EJ.js.map} +0 -0
  257. /package/dist/{chunk-SFDBLCZV.js.map → chunk-K4TWTUEY.js.map} +0 -0
  258. /package/dist/{chunk-T6RQNV7E.js.map → chunk-KHOINMCN.js.map} +0 -0
  259. /package/dist/{chunk-VDX2J7OX.js.map → chunk-KQAFEZQX.js.map} +0 -0
  260. /package/dist/{chunk-MZOQ26LH.js.map → chunk-LQGX5TCI.js.map} +0 -0
  261. /package/dist/{chunk-TRN56SPQ.js.map → chunk-M2OPC4BD.js.map} +0 -0
  262. /package/dist/{chunk-LXJZUNLX.js.map → chunk-NBK24POV.js.map} +0 -0
  263. /package/dist/{chunk-7X7A7KVN.js.map → chunk-NLYOOGZ7.js.map} +0 -0
  264. /package/dist/{chunk-G5I5ZT6O.js.map → chunk-OSDAF7HH.js.map} +0 -0
  265. /package/dist/{chunk-EZ3F4YIN.js.map → chunk-OV7EYM6M.js.map} +0 -0
  266. /package/dist/{chunk-AU43D3IT.js.map → chunk-P57VR7AI.js.map} +0 -0
  267. /package/dist/{chunk-NT2Q6AHC.js.map → chunk-PCO3P7WJ.js.map} +0 -0
  268. /package/dist/{chunk-73R4HOOK.js.map → chunk-PT6DQK5T.js.map} +0 -0
  269. /package/dist/{chunk-O43AAJFQ.js.map → chunk-PTGPU4DC.js.map} +0 -0
  270. /package/dist/{chunk-STQC2FYQ.js.map → chunk-PWVBVIOP.js.map} +0 -0
  271. /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
  272. /package/dist/{chunk-SIGDETMB.js.map → chunk-R4NGLTUS.js.map} +0 -0
  273. /package/dist/{chunk-S2GR3T7W.js.map → chunk-RNYVAQX2.js.map} +0 -0
  274. /package/dist/{chunk-PZ2QQULY.js.map → chunk-RU744TNX.js.map} +0 -0
  275. /package/dist/{chunk-2OFDC3AK.js.map → chunk-RVQVWDSN.js.map} +0 -0
  276. /package/dist/{chunk-OFGTRDWX.js.map → chunk-SIYZDT2V.js.map} +0 -0
  277. /package/dist/{chunk-PQ7EB5AD.js.map → chunk-SQPC6MUM.js.map} +0 -0
  278. /package/dist/{chunk-5R4YPRAR.js.map → chunk-TGF43K66.js.map} +0 -0
  279. /package/dist/{chunk-ZRUC7FD3.js.map → chunk-U4SG4HKQ.js.map} +0 -0
  280. /package/dist/{chunk-ATFFWIKL.js.map → chunk-U6HWKOGD.js.map} +0 -0
  281. /package/dist/{chunk-IOMLGJHG.js.map → chunk-UK5EH7AI.js.map} +0 -0
  282. /package/dist/{chunk-DB6XFMOF.js.map → chunk-VC4J4UTI.js.map} +0 -0
  283. /package/dist/{chunk-T32YYUT2.js.map → chunk-VQTPEL7O.js.map} +0 -0
  284. /package/dist/{chunk-7YQR6XHY.js.map → chunk-W6K6BC3H.js.map} +0 -0
  285. /package/dist/{chunk-FYX2LXIX.js.map → chunk-WI37MWRZ.js.map} +0 -0
  286. /package/dist/{chunk-7NBUE5VY.js.map → chunk-WPMDNCGB.js.map} +0 -0
  287. /package/dist/{chunk-GZ2KV2RO.js.map → chunk-WWLAKRMX.js.map} +0 -0
  288. /package/dist/{chunk-EWWK65CI.js.map → chunk-WY4IV62O.js.map} +0 -0
  289. /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
  290. /package/dist/{chunk-GQ6ZF4LD.js.map → chunk-XG4XXI66.js.map} +0 -0
  291. /package/dist/{chunk-Z6EZCN6Z.js.map → chunk-Y7427SL7.js.map} +0 -0
  292. /package/dist/{chunk-TXTRHF3B.js.map → chunk-YAPTJQI7.js.map} +0 -0
  293. /package/dist/{chunk-JRDL3FGM.js.map → chunk-YFET6A4I.js.map} +0 -0
  294. /package/dist/{chunk-ISX7GVZE.js.map → chunk-YRULQW5J.js.map} +0 -0
  295. /package/dist/{chunk-VDVHIRCK.js.map → chunk-YVHNBOK2.js.map} +0 -0
@@ -0,0 +1,387 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, rename, rm, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { getMemoryProjectionPath } from "./memory-projection-store.js";
7
+ import { openBetterSqlite3 } from "./runtime/better-sqlite.js";
8
+ import { recordAbstractionNode } from "./abstraction-nodes.js";
9
+ import { searchHarmonicRetrieval, type HarmonicRetrievalResult } from "./harmonic-retrieval.js";
10
+ import { rebuildMemoryProjection } from "./maintenance/rebuild-memory-projection.js";
11
+ import { StorageManager } from "./storage.js";
12
+
13
+ test("indexed harmonic projection matches the corpus scan and still sees later sources", async () => {
14
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
15
+ try {
16
+ const storage = new StorageManager(memoryDir);
17
+ await storage.ensureDirectories();
18
+ const active = await storage.writeMemory("fact", "active harmonic source payload", { source: "test" });
19
+ const archived = await storage.writeMemory("fact", "archived harmonic source payload", { source: "test" });
20
+ await storage.updateMemoryFrontmatter(archived.id, { status: "archived" });
21
+ await recordAbstractionNode({
22
+ memoryDir,
23
+ node: {
24
+ schemaVersion: 1,
25
+ nodeId: "mixed-sources",
26
+ recordedAt: "2026-10-12T00:00:00.000Z",
27
+ sessionKey: "agent:index",
28
+ kind: "topic",
29
+ abstractionLevel: "meso",
30
+ title: "stored title",
31
+ summary: "stored summary",
32
+ sourceMemoryIds: [active.id, archived.id, "missing-source"],
33
+ },
34
+ });
35
+
36
+ const beforeIndex = await searchHarmonicRetrieval({
37
+ memoryDir,
38
+ query: "active harmonic source payload",
39
+ maxResults: 5,
40
+ anchorsEnabled: false,
41
+ });
42
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
43
+ const originalReadAll = StorageManager.prototype.readAllMemories;
44
+ let corpusScans = 0;
45
+ StorageManager.prototype.readAllMemories = function (this: StorageManager, ...args: unknown[]) {
46
+ corpusScans += 1;
47
+ return Reflect.apply(originalReadAll, this, args);
48
+ };
49
+ let afterIndex: HarmonicRetrievalResult[];
50
+ try {
51
+ afterIndex = await searchHarmonicRetrieval({
52
+ memoryDir,
53
+ query: "active harmonic source payload",
54
+ maxResults: 5,
55
+ anchorsEnabled: false,
56
+ });
57
+ } finally {
58
+ StorageManager.prototype.readAllMemories = originalReadAll;
59
+ }
60
+ assert.equal(corpusScans, 0, "an openable fresh index must not scan the hot corpus");
61
+ assert.deepEqual(afterIndex, beforeIndex);
62
+ assert.deepEqual(afterIndex.map((result) => result.node.sourceMemoryIds), [[active.id]]);
63
+ assert.equal(afterIndex[0]?.node.title.includes("active harmonic source payload"), true);
64
+
65
+ const late = await storage.writeMemory("fact", "late harmonic source payload", { source: "test" });
66
+ await recordAbstractionNode({
67
+ memoryDir,
68
+ node: {
69
+ schemaVersion: 1,
70
+ nodeId: "late-source",
71
+ recordedAt: "2026-10-12T01:00:00.000Z",
72
+ sessionKey: "agent:index",
73
+ kind: "topic",
74
+ abstractionLevel: "meso",
75
+ title: "late title",
76
+ summary: "late summary",
77
+ sourceMemoryIds: [late.id],
78
+ },
79
+ });
80
+ const lateResults = await searchHarmonicRetrieval({
81
+ memoryDir,
82
+ query: "late harmonic source payload",
83
+ maxResults: 5,
84
+ anchorsEnabled: false,
85
+ });
86
+ assert.equal(lateResults.some((result) => result.node.nodeId === "late-source" && result.node.sourceMemoryIds?.includes(late.id)), true);
87
+
88
+ const coldMemory = (await storage.readAllMemories()).find((memory) => memory.frontmatter.id === active.id);
89
+ if (!coldMemory) throw new Error("active source missing before cold move");
90
+ const coldPath = path.join(memoryDir, "cold", path.relative(memoryDir, coldMemory.path));
91
+ await mkdir(path.dirname(coldPath), { recursive: true });
92
+ await rename(coldMemory.path, coldPath);
93
+ storage.invalidateMemoryCachesForTiers(["hot", "cold"]);
94
+ const coldResults = await searchHarmonicRetrieval({
95
+ memoryDir,
96
+ query: "active harmonic source payload",
97
+ maxResults: 5,
98
+ anchorsEnabled: false,
99
+ });
100
+ assert.equal(coldResults.some((result) => result.node.sourceMemoryIds?.includes(active.id)), true);
101
+ } finally {
102
+ await rm(memoryDir, { recursive: true, force: true });
103
+ }
104
+ });
105
+
106
+ function setRebuiltAt(memoryDir: string, iso: string): void {
107
+ const db = openBetterSqlite3(getMemoryProjectionPath(memoryDir));
108
+ try {
109
+ db.prepare("UPDATE meta SET value = ? WHERE key = 'rebuiltAt'").run(iso);
110
+ } finally {
111
+ db.close();
112
+ }
113
+ }
114
+
115
+ async function nodeFor(memoryDir: string, nodeId: string, sourceMemoryIds: string[], title: string) {
116
+ await recordAbstractionNode({
117
+ memoryDir,
118
+ node: {
119
+ schemaVersion: 1,
120
+ nodeId,
121
+ recordedAt: "2026-10-12T00:00:00.000Z",
122
+ sessionKey: "agent:index",
123
+ kind: "topic",
124
+ abstractionLevel: "meso",
125
+ title,
126
+ summary: title,
127
+ sourceMemoryIds,
128
+ },
129
+ });
130
+ }
131
+
132
+ const search = (memoryDir: string, query: string) =>
133
+ searchHarmonicRetrieval({ memoryDir, query, maxResults: 5, anchorsEnabled: false });
134
+
135
+ test("a source written after the rebuild stays found beyond two days, and unsafe or queue ids never resolve", async () => {
136
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
137
+ try {
138
+ const storage = new StorageManager(memoryDir);
139
+ await storage.ensureDirectories();
140
+ const base = await storage.writeMemory("fact", "baseline harmonic anchor", { source: "test" });
141
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
142
+
143
+ // Written after the rebuild, then filed under a day three days back; the
144
+ // index was rebuilt four days ago, so that day is inside the probe window.
145
+ const late = await storage.writeMemory("fact", "aged unindexed zebra source", { source: "test" });
146
+ const lateMemory = (await storage.readAllMemories()).find((m) => m.frontmatter.id === late.id);
147
+ if (!lateMemory) throw new Error("late source missing");
148
+ const day = new Date(Date.now() - 3 * 86_400_000).toISOString().slice(0, 10);
149
+ const agedPath = path.join(memoryDir, "facts", day, `${late.id}.md`);
150
+ await mkdir(path.dirname(agedPath), { recursive: true });
151
+ await rename(lateMemory.path, agedPath);
152
+ storage.invalidateMemoryCachesForTiers(["hot", "cold"]);
153
+ setRebuiltAt(memoryDir, new Date(Date.now() - 4 * 86_400_000).toISOString());
154
+ await nodeFor(memoryDir, "aged-node", [late.id], "aged unindexed zebra source");
155
+ const aged = await search(memoryDir, "aged unindexed zebra source");
156
+ assert.equal(aged.some((r) => r.node.nodeId === "aged-node" && r.node.sourceMemoryIds?.includes(late.id)), true);
157
+
158
+ // Path traversal ids and question-queue ids are dropped, exactly as the
159
+ // corpus scan drops them.
160
+ const questionId = await storage.writeQuestion("queued zebra question", "ctx", 1);
161
+ await nodeFor(memoryDir, "unsafe-node", ["../facts/" + base.id, "../../etc/passwd"], "unsafe traversal cue");
162
+ await nodeFor(memoryDir, "question-node", [questionId], "queue leak cue");
163
+ setRebuiltAt(memoryDir, new Date().toISOString());
164
+ for (const query of ["unsafe traversal cue", "queue leak cue"]) {
165
+ assert.deepEqual(await search(memoryDir, query), [], query);
166
+ }
167
+ } finally {
168
+ await rm(memoryDir, { recursive: true, force: true });
169
+ }
170
+ });
171
+
172
+ test("an index older than a week falls back to the corpus scan and still finds an aged source", async () => {
173
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
174
+ try {
175
+ const storage = new StorageManager(memoryDir);
176
+ await storage.ensureDirectories();
177
+ const source = await storage.writeMemory("fact", "stale index quokka source", { source: "test" });
178
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
179
+ setRebuiltAt(memoryDir, new Date(Date.now() - 30 * 86_400_000).toISOString());
180
+ await nodeFor(memoryDir, "stale-node", [source.id], "stale index quokka source");
181
+ const results = await search(memoryDir, "stale index quokka source");
182
+ assert.equal(results.some((r) => r.node.nodeId === "stale-node"), true);
183
+ } finally {
184
+ await rm(memoryDir, { recursive: true, force: true });
185
+ }
186
+ });
187
+
188
+ // Counts hot-corpus scans while `run` executes.
189
+ async function countCorpusScans<T>(run: () => Promise<T>): Promise<{ value: T; scans: number }> {
190
+ const original = StorageManager.prototype.readAllMemories;
191
+ let scans = 0;
192
+ StorageManager.prototype.readAllMemories = function (this: StorageManager, ...args: unknown[]) {
193
+ scans += 1;
194
+ return Reflect.apply(original, this, args);
195
+ };
196
+ try {
197
+ return { value: await run(), scans };
198
+ } finally {
199
+ StorageManager.prototype.readAllMemories = original;
200
+ }
201
+ }
202
+
203
+ test("a scoped or pre-complete projection is not trusted and falls back to the corpus scan", async () => {
204
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
205
+ try {
206
+ const storage = new StorageManager(memoryDir);
207
+ await storage.ensureDirectories();
208
+ const source = await storage.writeMemory("fact", "scoped rebuild okapi source", { source: "test" });
209
+ await nodeFor(memoryDir, "okapi-node", [source.id], "scoped rebuild okapi source");
210
+
211
+ // First projection built with a scope that excludes the source: rebuiltAt is
212
+ // fresh but memory_current does not hold it.
213
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false, updatedBefore: "2000-01-01T00:00:00.000Z" });
214
+ const scoped = await countCorpusScans(() => search(memoryDir, "scoped rebuild okapi source"));
215
+ assert.equal(scoped.value.some((r) => r.node.nodeId === "okapi-node"), true, "out-of-scope source must still resolve");
216
+ assert.equal(scoped.scans > 0, true, "an incomplete index must fall back to the scan");
217
+
218
+ // An unscoped rebuild is complete and takes the index path.
219
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
220
+ const full = await countCorpusScans(() => search(memoryDir, "scoped rebuild okapi source"));
221
+ assert.equal(full.value.some((r) => r.node.nodeId === "okapi-node"), true);
222
+ assert.equal(full.scans, 0);
223
+
224
+ // A projection written before the `complete` flag existed has no such key.
225
+ const db = openBetterSqlite3(getMemoryProjectionPath(memoryDir));
226
+ try {
227
+ db.prepare("DELETE FROM meta WHERE key = 'complete'").run();
228
+ } finally {
229
+ db.close();
230
+ }
231
+ const legacy = await countCorpusScans(() => search(memoryDir, "scoped rebuild okapi source"));
232
+ assert.equal(legacy.value.some((r) => r.node.nodeId === "okapi-node"), true);
233
+ assert.equal(legacy.scans > 0, true, "a projection without the flag must fall back");
234
+ } finally {
235
+ await rm(memoryDir, { recursive: true, force: true });
236
+ }
237
+ });
238
+
239
+ test("a legacy memory whose id contains a slash still resolves through the projection", async () => {
240
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
241
+ try {
242
+ const storage = new StorageManager(memoryDir);
243
+ await storage.ensureDirectories();
244
+ const slashId = "legacy/slash-id";
245
+ const day = new Date().toISOString().slice(0, 10);
246
+ await mkdir(path.join(memoryDir, "facts", day), { recursive: true });
247
+ await writeFile(
248
+ path.join(memoryDir, "facts", day, "legacy-slash-file.md"),
249
+ `---\nid: ${slashId}\ncategory: fact\ncreated: ${new Date().toISOString()}\nupdated: ${new Date().toISOString()}\nsource: test\nconfidence: 0.8\nconfidenceTier: implied\ntags: ["alpha"]\n---\n\nslash identifier narwhal source\n`,
250
+ "utf-8",
251
+ );
252
+ await nodeFor(memoryDir, "slash-node", [slashId], "slash identifier narwhal source");
253
+ const before = await search(memoryDir, "slash identifier narwhal source");
254
+ assert.equal(before.some((r) => r.node.nodeId === "slash-node"), true, "the corpus scan keys by frontmatter id");
255
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
256
+ const indexed = await countCorpusScans(() => search(memoryDir, "slash identifier narwhal source"));
257
+ assert.deepEqual(indexed.value, before);
258
+ assert.equal(indexed.scans, 0);
259
+ } finally {
260
+ await rm(memoryDir, { recursive: true, force: true });
261
+ }
262
+ });
263
+
264
+ function memoryFileText(id: string, body: string): string {
265
+ const now = new Date().toISOString();
266
+ return `---\nid: ${id}\ncategory: fact\ncreated: ${now}\nupdated: ${now}\nsource: test\nconfidence: 0.8\nconfidenceTier: implied\ntags: ["alpha"]\n---\n\n${body}\n`;
267
+ }
268
+
269
+ test("direct probes reject symlinked and artifact sources like the scan, and an old-dated post-rebuild source is still found", async () => {
270
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
271
+ const outsideDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-outside-"));
272
+ try {
273
+ const storage = new StorageManager(memoryDir);
274
+ await storage.ensureDirectories();
275
+ const day = new Date().toISOString().slice(0, 10);
276
+ await mkdir(path.join(memoryDir, "facts", day), { recursive: true });
277
+ await mkdir(path.join(memoryDir, "artifacts", day), { recursive: true });
278
+
279
+ // A source file outside the namespace root, reachable through a symlink.
280
+ await writeFile(path.join(outsideDir, "outside-sym.md"), memoryFileText("outside-sym", "symlink escape cue body"), "utf-8");
281
+ await symlink(path.join(outsideDir, "outside-sym.md"), path.join(memoryDir, "facts", day, "outside-sym.md"));
282
+ // An artifact: outside the recall surface the corpus scan reads.
283
+ await writeFile(path.join(memoryDir, "artifacts", day, "art-1.md"), memoryFileText("art-1", "artifact leak cue body"), "utf-8");
284
+ await nodeFor(memoryDir, "symlink-node", ["outside-sym"], "symlink escape cue");
285
+ await nodeFor(memoryDir, "artifact-node", ["art-1"], "artifact leak cue");
286
+
287
+ const scanSymlink = await search(memoryDir, "symlink escape cue");
288
+ const scanArtifact = await search(memoryDir, "artifact leak cue");
289
+ assert.deepEqual(scanSymlink, [], "the corpus scan must not follow the symlink");
290
+ assert.deepEqual(scanArtifact, [], "the corpus scan must not read artifacts");
291
+
292
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
293
+ const indexed = await countCorpusScans(async () => ({
294
+ symlink: await search(memoryDir, "symlink escape cue"),
295
+ artifact: await search(memoryDir, "artifact leak cue"),
296
+ }));
297
+ assert.deepEqual(indexed.value.symlink, scanSymlink);
298
+ assert.deepEqual(indexed.value.artifact, scanArtifact);
299
+ assert.equal(indexed.scans, 0, "this was the index path, not a fallback");
300
+
301
+ // Written after the rebuild but filed under a date far outside the probe
302
+ // window (an import that keeps its original created date).
303
+ await mkdir(path.join(memoryDir, "facts", "2020-01-01"), { recursive: true });
304
+ await writeFile(
305
+ path.join(memoryDir, "facts", "2020-01-01", "imported-old.md"),
306
+ memoryFileText("imported-old", "imported older marmot cue body"),
307
+ "utf-8",
308
+ );
309
+ storage.invalidateMemoryCachesForTiers(["hot", "cold"]);
310
+ await nodeFor(memoryDir, "imported-node", ["imported-old"], "imported older marmot cue");
311
+ const old = await search(memoryDir, "imported older marmot cue");
312
+ assert.equal(old.some((r) => r.node.nodeId === "imported-node" && r.node.sourceMemoryIds?.includes("imported-old")), true);
313
+ } finally {
314
+ await rm(memoryDir, { recursive: true, force: true });
315
+ await rm(outsideDir, { recursive: true, force: true });
316
+ }
317
+ });
318
+
319
+ test("a dangling source id costs one path walk, then is remembered as absent", async () => {
320
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
321
+ try {
322
+ const storage = new StorageManager(memoryDir);
323
+ await storage.ensureDirectories();
324
+ const real = await storage.writeMemory("fact", "real source wombat body", { source: "test" });
325
+ await nodeFor(memoryDir, "real-node", [real.id, "dangling-id"], "real source wombat");
326
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
327
+
328
+ const original = StorageManager.prototype.collectActiveMemoryPaths;
329
+ let walks = 0;
330
+ StorageManager.prototype.collectActiveMemoryPaths = function (this: StorageManager, ...args: unknown[]) {
331
+ walks += 1;
332
+ return Reflect.apply(original, this, args);
333
+ };
334
+ try {
335
+ const first = await search(memoryDir, "real source wombat");
336
+ assert.equal(first.some((r) => r.node.nodeId === "real-node" && r.node.sourceMemoryIds?.includes(real.id)), true);
337
+ assert.equal(walks, 1, "the unresolved id is looked up by one walk");
338
+ await search(memoryDir, "real source wombat");
339
+ assert.equal(walks, 1, "a confirmed-absent id is not walked again within the TTL");
340
+ } finally {
341
+ StorageManager.prototype.collectActiveMemoryPaths = original;
342
+ }
343
+ } finally {
344
+ await rm(memoryDir, { recursive: true, force: true });
345
+ }
346
+ });
347
+
348
+
349
+ test("a separator-containing id written after the rebuild is still placed, by the scan", async () => {
350
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
351
+ try {
352
+ const storage = new StorageManager(memoryDir);
353
+ await storage.ensureDirectories();
354
+ await rebuildMemoryProjection({ memoryDir, storage, dryRun: false });
355
+ const day = new Date().toISOString().slice(0, 10);
356
+ await mkdir(path.join(memoryDir, "facts", day), { recursive: true });
357
+ await writeFile(
358
+ path.join(memoryDir, "facts", day, "capsule-import-file.md"),
359
+ memoryFileText("capsule/import-id", "capsule import ocelot source body"),
360
+ "utf-8",
361
+ );
362
+ storage.invalidateMemoryCachesForTiers(["hot", "cold"]);
363
+ await nodeFor(memoryDir, "capsule-node", ["capsule/import-id"], "capsule import ocelot source");
364
+ const results = await search(memoryDir, "capsule import ocelot source");
365
+ assert.equal(results.some((r) => r.node.nodeId === "capsule-node" && r.node.sourceMemoryIds?.includes("capsule/import-id")), true);
366
+ } finally {
367
+ await rm(memoryDir, { recursive: true, force: true });
368
+ }
369
+ });
370
+
371
+ test("the cold path walk honors an aborted signal", async () => {
372
+ const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-harmonic-index-"));
373
+ try {
374
+ const storage = new StorageManager(memoryDir);
375
+ await storage.ensureDirectories();
376
+ await mkdir(path.join(memoryDir, "cold", "facts", "2020-01-01"), { recursive: true });
377
+ await writeFile(
378
+ path.join(memoryDir, "cold", "facts", "2020-01-01", "cold-1.md"),
379
+ memoryFileText("cold-1", "cold walk abort body"),
380
+ "utf-8",
381
+ );
382
+ assert.equal((await storage.collectColdMemoryPaths()).length, 1);
383
+ await assert.rejects(() => storage.collectColdMemoryPaths({ abortSignal: AbortSignal.abort() }));
384
+ } finally {
385
+ await rm(memoryDir, { recursive: true, force: true });
386
+ }
387
+ });
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { realpath } from "node:fs/promises";
2
4
  import { listJsonFilesStrict, readJsonFile } from "./json-store.js";
3
5
  import { throwIfAborted } from "./abort-error.js";
4
6
  import { inferMemoryStatus, toMemoryPathRel } from "./memory-lifecycle-ledger-utils.js";
@@ -13,30 +15,168 @@ import { compareDeterministicStrings } from "./deterministic-order.js";
13
15
  import { countRecallTokenOverlap, normalizeRecallTokens } from "./recall-tokenization.js";
14
16
  import { stripAttributesSuffix } from "./structured-attributes.js";
15
17
  import { isValidityExpiredNow } from "./temporal-validity.js";
16
- import { isRecord } from "./store-contract.js";
18
+ import { assertSafePathSegment, isRecord } from "./store-contract.js";
17
19
  import { isSupportPassportPrivateMemory } from "./support-passport/card-projection.js";
18
20
  import type { MemoryFile } from "./types.js";
21
+ import { readProjectedMemoryStates } from "./memory-projection-batch.js";
22
+ import { SecureStoreLockedError } from "./secure-store/secure-fs.js";
23
+ import { StorageManager } from "./storage.js";
24
+ import { RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
25
+ import { isPathInsideStorageRoot } from "./storage-paths.js";
26
+
19
27
 
20
28
  type SourceMemoryMap = Map<string, MemoryFile>;
21
29
 
22
- async function readSourceMemories(options: {
30
+ // `rootReal` set: a direct probe, which skips the corpus walk's symlink
31
+ // hardening, so the resolved file must stay inside the memory root. Null: the
32
+ // path came from the contained corpus walk and is already trusted.
33
+ async function readMemoryFile(
34
+ storage: StorageManager,
35
+ rootReal: string | null,
36
+ filePath: string,
37
+ abortSignal?: AbortSignal,
38
+ ): Promise<MemoryFile | null> {
39
+ throwIfAborted(abortSignal, "harmonic retrieval aborted");
40
+ if (!existsSync(filePath)) return null;
41
+ try {
42
+ if (rootReal !== null && !isPathInsideStorageRoot(rootReal, await realpath(filePath))) return null;
43
+ return await storage.readMemoryByPath(filePath);
44
+ } catch (err) {
45
+ throwIfAborted(abortSignal, "harmonic retrieval aborted");
46
+ if (err instanceof SecureStoreLockedError) throw err;
47
+ return null;
48
+ }
49
+ }
50
+
51
+ // An index older than this is treated as unusable: the date window needed to
52
+ // cover "created since the last rebuild" would stop being cheap.
53
+ const INDEX_MAX_AGE_MS = 7 * 86_400_000;
54
+ const DAY_MS = 86_400_000;
55
+
56
+ function utcDaysSince(rebuiltMs: number, nowMs: number): string[] {
57
+ const days: string[] = [];
58
+ for (let day = Math.floor((rebuiltMs - DAY_MS) / DAY_MS) * DAY_MS; day <= nowMs; day += DAY_MS) {
59
+ days.push(new Date(day).toISOString().slice(0, 10));
60
+ }
61
+ return days;
62
+ }
63
+
64
+ // Source ids confirmed absent by a corpus path walk. A node only references a
65
+ // source after the source file exists, so a confirmed absence stays true unless
66
+ // the file is restored later; the short TTL bounds that. Without this, one
67
+ // dangling id (a hard-deleted fact) would force a walk on every recall.
68
+ const ABSENT_SOURCE_TTL_MS = 10 * 60_000;
69
+ const ABSENT_SOURCE_MAX_ENTRIES = 4096;
70
+ const absentSourceIds = new Map<string, number>(); // `${memoryDir}\0${id}` -> expiry ms
71
+
72
+ // Memories the projection has no row for were written after its last rebuild, so
73
+ // their dated directory is normally one of `days`. Hot and cold tiers are probed
74
+ // by exact path. Only recall-corpus categories are probed: `questions/` is a queue
75
+ // and `artifacts/` is outside the recall surface, so neither can be a source.
76
+ function unprojectedCandidatePaths(memoryDir: string, id: string, days: string[]): string[] {
77
+ const paths: string[] = [];
78
+ for (const root of [memoryDir, path.join(memoryDir, "cold")]) {
79
+ for (const dir of RECALL_FALLBACK_DIRS) {
80
+ paths.push(path.join(root, dir, `${id}.md`));
81
+ for (const day of days) paths.push(path.join(root, dir, day, `${id}.md`));
82
+ }
83
+ }
84
+ return paths;
85
+ }
86
+
87
+ /**
88
+ * Resolve only the source ids the abstraction nodes reference. Returns null when
89
+ * the projection is absent, unreadable, too old, or not known to cover every
90
+ * active memory (a scoped or pre-`complete` rebuild), so the caller keeps the
91
+ * full-corpus scan. Every file is read from disk, so status, tags and validity
92
+ * are current; the index only says where to look. An id the cheap probes cannot
93
+ * place (an import filed under an old date, a tier root outside the memory dir)
94
+ * is looked up by one contained path-only walk, never a frontmatter parse of the
95
+ * corpus.
96
+ */
97
+ async function readIndexedSourceMemories(options: {
98
+ memoryDir: string;
99
+ abortSignal?: AbortSignal;
100
+ sourceMemoryIds: string[];
101
+ nowMs: number;
102
+ }): Promise<SourceMemoryMap | null> {
103
+ // The ids go to SQL as bound parameters, so any id the projection holds is safe to look up.
104
+ const batch = readProjectedMemoryStates(options.memoryDir, options.sourceMemoryIds);
105
+ const rebuiltMs = Date.parse(batch?.rebuiltAt ?? "");
106
+ if (!batch?.complete || !Number.isFinite(rebuiltMs) || options.nowMs - rebuiltMs > INDEX_MAX_AGE_MS) return null;
107
+ const rootReal = await realpath(options.memoryDir).catch(() => null);
108
+ if (rootReal === null) return null;
109
+
110
+ const storage = new StorageManager(options.memoryDir);
111
+ const days = utcDaysSince(rebuiltMs, options.nowMs);
112
+ const sourceMemories: SourceMemoryMap = new Map();
113
+ const pending: string[] = [];
114
+ for (const id of options.sourceMemoryIds) {
115
+ const state = batch.states.get(id);
116
+ // An id is only ever joined into a path when it is a single safe segment.
117
+ let safeId = true;
118
+ try {
119
+ assertSafePathSegment(id, "sourceMemoryId");
120
+ } catch {
121
+ safeId = false;
122
+ }
123
+ // An unprojected id that is not a single path segment cannot be probed or
124
+ // matched by filename, and the scan keys sources by frontmatter id, so only
125
+ // the scan can place it. Rare (legacy/imported ids); worst case is the old cost.
126
+ if (!state && !safeId) return null;
127
+ // state.path was containment-checked by the projection reader.
128
+ const candidates = state
129
+ ? [state.path, path.join(options.memoryDir, "cold", state.pathRel)]
130
+ : unprojectedCandidatePaths(options.memoryDir, id, days);
131
+ let found = false;
132
+ for (const candidate of candidates) {
133
+ const memory = await readMemoryFile(storage, rootReal, candidate, options.abortSignal);
134
+ if (memory?.frontmatter.id !== id) continue;
135
+ sourceMemories.set(id, memory);
136
+ found = true;
137
+ break;
138
+ }
139
+ const absentKey = `${options.memoryDir}\0${id}`;
140
+ if (!found && safeId && (absentSourceIds.get(absentKey) ?? 0) <= options.nowMs) pending.push(id);
141
+ }
142
+ if (pending.length === 0) return sourceMemories;
143
+
144
+ const wanted = new Set(pending);
145
+ const walked = [
146
+ ...(await storage.collectActiveMemoryPaths({ abortSignal: options.abortSignal })),
147
+ ...(await storage.collectColdMemoryPaths({ abortSignal: options.abortSignal })),
148
+ ];
149
+ for (const filePath of walked) {
150
+ throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
151
+ if (!wanted.has(path.basename(filePath, ".md"))) continue;
152
+ const memory = await readMemoryFile(storage, null, filePath, options.abortSignal);
153
+ if (memory && wanted.has(memory.frontmatter.id)) sourceMemories.set(memory.frontmatter.id, memory);
154
+ }
155
+ if (absentSourceIds.size > ABSENT_SOURCE_MAX_ENTRIES) absentSourceIds.clear();
156
+ for (const id of pending) {
157
+ if (!sourceMemories.has(id)) absentSourceIds.set(`${options.memoryDir}\0${id}`, options.nowMs + ABSENT_SOURCE_TTL_MS);
158
+ }
159
+ return sourceMemories;
160
+ }
161
+
162
+ async function readCorpusSourceMemories(options: {
23
163
  memoryDir: string;
24
164
  abortSignal?: AbortSignal;
25
165
  }): Promise<SourceMemoryMap> {
26
166
  const sourceMemories: SourceMemoryMap = new Map();
27
- try {
28
- const { StorageManager } = await import("./storage.js");
29
- const storage = new StorageManager(options.memoryDir);
30
- const [hotMemories, coldMemories] = await Promise.all([
31
- storage.readAllMemories({ abortSignal: options.abortSignal }),
32
- storage.readAllColdMemories(),
33
- ]);
34
- for (const memory of [...hotMemories, ...coldMemories]) {
167
+ try {
168
+ const storage = new StorageManager(options.memoryDir);
169
+ const [hotMemories, coldMemories] = await Promise.all([
170
+ storage.readAllMemories({ abortSignal: options.abortSignal }),
171
+ storage.readAllColdMemories(),
172
+ ]);
173
+ for (const memory of [...hotMemories, ...coldMemories]) {
35
174
  throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
36
175
  sourceMemories.set(memory.frontmatter.id, memory);
37
176
  }
38
- } catch {
177
+ } catch (err) {
39
178
  throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
179
+ if (err instanceof SecureStoreLockedError) return new Map();
40
180
  // Source validation fails closed when the authorized memory directory is
41
181
  // unreadable. Source-less nodes remain eligible for retrieval.
42
182
  sourceMemories.clear();
@@ -44,6 +184,27 @@ async function readSourceMemories(options: {
44
184
  return sourceMemories;
45
185
  }
46
186
 
187
+ async function readSourceMemories(options: {
188
+ memoryDir: string;
189
+ abortSignal?: AbortSignal;
190
+ sourceMemoryIds: string[];
191
+ nowMs?: number;
192
+ }): Promise<SourceMemoryMap> {
193
+ try {
194
+ const indexed = await readIndexedSourceMemories({
195
+ memoryDir: options.memoryDir,
196
+ abortSignal: options.abortSignal,
197
+ sourceMemoryIds: options.sourceMemoryIds,
198
+ nowMs: options.nowMs ?? Date.now(),
199
+ });
200
+ if (indexed) return indexed;
201
+ } catch (err) {
202
+ throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
203
+ if (err instanceof SecureStoreLockedError) return new Map();
204
+ }
205
+ return readCorpusSourceMemories(options);
206
+ }
207
+
47
208
  function parseSourceMemoryInsertedAt(node: AbstractionNode): Map<string, string> {
48
209
  const insertedAtById = new Map<string, string>();
49
210
  const insertedAtRaw = node.metadata?.[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY];
@@ -304,10 +465,15 @@ export async function readProjectedHarmonicGraph(options: {
304
465
  }): Promise<ProjectedHarmonicGraph> {
305
466
  throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
306
467
  const nodes = await readAbstractionNodes(options);
307
- const sourceBacked = nodes.some((node) => (node.sourceMemoryIds?.length ?? 0) > 0);
308
- const sourceMemories: SourceMemoryMap = sourceBacked
309
- ? await readSourceMemories(options)
310
- : new Map<string, MemoryFile>();
468
+ const sourceMemoryIds = [...new Set(nodes.flatMap((node) => node.sourceMemoryIds ?? []))];
469
+ const sourceMemories: SourceMemoryMap = sourceMemoryIds.length > 0
470
+ ? await readSourceMemories({
471
+ memoryDir: options.memoryDir,
472
+ abortSignal: options.abortSignal,
473
+ sourceMemoryIds,
474
+ nowMs: options.nowMs,
475
+ })
476
+ : new Map();
311
477
  const nowMs = options.nowMs ?? Date.now();
312
478
  // A node whose full stored source set survived projection keeps legacy
313
479
  // anchor attribution (an anchor written before per-node source ids existed).
@@ -755,6 +755,7 @@ function writeProjectionDb(
755
755
  governance: Awaited<ReturnType<typeof loadLatestGovernanceProjection>>,
756
756
  usedLifecycleLedger: boolean,
757
757
  sourceLifecycleLedgerEventCount: number,
758
+ complete: boolean,
758
759
  ): void {
759
760
  const db = openBetterSqlite3(dbPath);
760
761
  const memoryDir = path.dirname(path.dirname(dbPath));
@@ -765,6 +766,7 @@ function writeProjectionDb(
765
766
  insertMeta.run("schemaVersion", String(MEMORY_PROJECTION_SCHEMA_VERSION));
766
767
  insertMeta.run("rebuiltAt", nowIso);
767
768
  insertMeta.run("usedLifecycleLedger", usedLifecycleLedger ? "true" : "false");
769
+ insertMeta.run("complete", complete ? "true" : "false");
768
770
  writeProjectionMetaHighWater(insertMeta, sourceLifecycleLedgerEventCount, timelineRows);
769
771
  insertMeta.run("latestGovernanceRunId", governance?.runId ?? "");
770
772
 
@@ -1126,6 +1128,7 @@ export async function rebuildMemoryProjection(
1126
1128
  nextGovernance,
1127
1129
  snapshot.usedLifecycleLedger,
1128
1130
  snapshot.sourceLifecycleLedgerEventCount,
1131
+ !hasScopedProjectionFilter(snapshot.scope),
1129
1132
  );
1130
1133
  backupPath = await backupExistingProjection(options.memoryDir, outputPath, now);
1131
1134
  backupPath = await commitPreparedFileAtomically(tempPath, outputPath, backupPath);