@remnic/core 9.3.712 → 9.3.714

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 (328) hide show
  1. package/dist/access-boundary.d.ts +6 -6
  2. package/dist/access-boundary.js +17 -16
  3. package/dist/access-cli.js +40 -39
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +5 -5
  6. package/dist/access-http.js +24 -23
  7. package/dist/access-mcp.d.ts +18 -5
  8. package/dist/access-mcp.js +21 -20
  9. package/dist/access-operations-batch.js +18 -17
  10. package/dist/access-operations.d.ts +6 -5
  11. package/dist/access-operations.js +20 -19
  12. package/dist/access-schema.d.ts +10 -0
  13. package/dist/access-schema.js +1 -1
  14. package/dist/{access-service-Dx2rSJjH.d.ts → access-service-lddjZoRh.d.ts} +20 -5
  15. package/dist/access-service.d.ts +5 -5
  16. package/dist/access-service.js +16 -15
  17. package/dist/access-surface-catalog.d.ts +5 -5
  18. package/dist/access-surface-catalog.js +6 -1
  19. package/dist/access-surface-catalog.js.map +1 -1
  20. package/dist/action-confidence.d.ts +1 -1
  21. package/dist/active-memory-bridge.d.ts +3 -1
  22. package/dist/active-memory-bridge.js +2 -1
  23. package/dist/active-recall.d.ts +1 -1
  24. package/dist/active-recall.js +4 -3
  25. package/dist/active-recall.js.map +1 -1
  26. package/dist/adapters/index.js +4 -4
  27. package/dist/adapters/registry.js +2 -2
  28. package/dist/behavior-learner.d.ts +1 -1
  29. package/dist/behavior-signals.d.ts +1 -1
  30. package/dist/bootstrap.d.ts +4 -4
  31. package/dist/briefing.d.ts +1 -1
  32. package/dist/briefing.js +8 -7
  33. package/dist/buffer-surprise-report.d.ts +1 -1
  34. package/dist/buffer.d.ts +1 -1
  35. package/dist/calibration.d.ts +1 -1
  36. package/dist/capabilities.d.ts +1 -1
  37. package/dist/{catalog-CadPvH29.d.ts → catalog-CbR0CsUw.d.ts} +1 -1
  38. package/dist/causal-behavior.d.ts +1 -1
  39. package/dist/causal-consolidation.d.ts +1 -1
  40. package/dist/causal-consolidation.js +9 -8
  41. package/dist/causal-consolidation.js.map +1 -1
  42. package/dist/{chunk-OMSTU233.js → chunk-2KAYTPPT.js} +4 -4
  43. package/dist/{chunk-VYZNWH4J.js → chunk-2SXMCQEQ.js} +3 -3
  44. package/dist/{chunk-NXL5CVE7.js → chunk-2Y6LMOQQ.js} +2 -2
  45. package/dist/{chunk-AMXOCZHE.js → chunk-3VUPWDSE.js} +15 -3
  46. package/dist/{chunk-AMXOCZHE.js.map → chunk-3VUPWDSE.js.map} +1 -1
  47. package/dist/{chunk-FVQJYWH7.js → chunk-5GKPFL3S.js} +1 -1
  48. package/dist/{chunk-FVQJYWH7.js.map → chunk-5GKPFL3S.js.map} +1 -1
  49. package/dist/{chunk-LQ4J7ELC.js → chunk-67HG7KFE.js} +2 -2
  50. package/dist/{chunk-ZBG4K36I.js → chunk-6FUJ2VYY.js} +3 -3
  51. package/dist/{chunk-GMRNKPWO.js → chunk-6QUPUTT7.js} +2 -2
  52. package/dist/{chunk-Z56IHRVV.js → chunk-6UU43ZGD.js} +2 -2
  53. package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
  54. package/dist/{chunk-OWOKOFXK.js → chunk-AJOIM6UR.js} +2 -2
  55. package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
  56. package/dist/{chunk-YVWZW37D.js → chunk-BNOAB5HH.js} +62 -12
  57. package/dist/{chunk-YVWZW37D.js.map → chunk-BNOAB5HH.js.map} +1 -1
  58. package/dist/{chunk-A2R5NV6O.js → chunk-CTAHKIG6.js} +205 -29
  59. package/dist/chunk-CTAHKIG6.js.map +1 -0
  60. package/dist/{chunk-K22IJO4R.js → chunk-CTOQEZSN.js} +2 -2
  61. package/dist/{chunk-SINGJCUR.js → chunk-CX77GNMB.js} +14 -3
  62. package/dist/chunk-CX77GNMB.js.map +1 -0
  63. package/dist/{chunk-HSDJCT3V.js → chunk-DXGS5YOQ.js} +3 -3
  64. package/dist/{chunk-HV57RHMD.js → chunk-EH5ED3C6.js} +2 -2
  65. package/dist/{chunk-NUWZFSKC.js → chunk-FVZ2ISIX.js} +9 -1
  66. package/dist/chunk-FVZ2ISIX.js.map +1 -0
  67. package/dist/{chunk-GKPIY7EQ.js → chunk-FXZKXW7C.js} +117 -46
  68. package/dist/chunk-FXZKXW7C.js.map +1 -0
  69. package/dist/{chunk-7NDYFAJS.js → chunk-GM3BMWKR.js} +2 -2
  70. package/dist/{chunk-ZVDRL6SK.js → chunk-HHLWSWWF.js} +2 -2
  71. package/dist/chunk-HHLWSWWF.js.map +1 -0
  72. package/dist/{chunk-5QUS24F7.js → chunk-I56LKMF7.js} +2 -2
  73. package/dist/{chunk-Z3B2YW56.js → chunk-IHMT6XTY.js} +9 -5
  74. package/dist/chunk-IHMT6XTY.js.map +1 -0
  75. package/dist/chunk-JAKIDQB2.js +173 -0
  76. package/dist/chunk-JAKIDQB2.js.map +1 -0
  77. package/dist/{chunk-35YJ6KCV.js → chunk-JEIROMLX.js} +2 -2
  78. package/dist/{chunk-SKQCFAYU.js → chunk-JVEMUX6L.js} +2 -2
  79. package/dist/{chunk-CXMXAC5R.js → chunk-K44MIN6D.js} +3 -3
  80. package/dist/{chunk-5KQCOIPW.js → chunk-MECU6WSH.js} +188 -31
  81. package/dist/chunk-MECU6WSH.js.map +1 -0
  82. package/dist/{chunk-BQCFXAMN.js → chunk-MPHDVHIB.js} +2 -2
  83. package/dist/{chunk-BOGENF7P.js → chunk-MSVSP5VO.js} +1 -1
  84. package/dist/chunk-MSVSP5VO.js.map +1 -0
  85. package/dist/{chunk-QXNFQKWU.js → chunk-MTHNJCOJ.js} +2 -2
  86. package/dist/{chunk-3TCRU4JA.js → chunk-O62P2LFL.js} +2 -2
  87. package/dist/{chunk-JSDZMOT7.js → chunk-OC56UWV5.js} +11 -11
  88. package/dist/{chunk-7IBEWQLG.js → chunk-QOIONSOI.js} +2 -2
  89. package/dist/{chunk-4P5UAL3C.js → chunk-RBHANGVX.js} +6 -6
  90. package/dist/{chunk-SK2CR6MW.js → chunk-RVLB4E6N.js} +2 -2
  91. package/dist/{chunk-I3BT2IDW.js → chunk-SYDHFFAW.js} +1110 -28
  92. package/dist/chunk-SYDHFFAW.js.map +1 -0
  93. package/dist/{chunk-VILEUJXC.js → chunk-UC4YOAC2.js} +74 -2
  94. package/dist/{chunk-VILEUJXC.js.map → chunk-UC4YOAC2.js.map} +1 -1
  95. package/dist/{chunk-CNVIWMQI.js → chunk-UY4JMEAK.js} +2 -2
  96. package/dist/{chunk-S6FQLQGH.js → chunk-V7GDTQ6I.js} +2 -2
  97. package/dist/{chunk-JNOYYWCA.js → chunk-VO4UICII.js} +3 -3
  98. package/dist/{chunk-54TI5GLV.js → chunk-WI4QX7GH.js} +3 -3
  99. package/dist/{chunk-3XHD3XGK.js → chunk-XCTDHF7U.js} +30 -2
  100. package/dist/chunk-XCTDHF7U.js.map +1 -0
  101. package/dist/{chunk-XKU4YE6Z.js → chunk-XZTUMWKQ.js} +2 -2
  102. package/dist/{cli-DTWRoJEI.d.ts → cli-iAiVkbYJ.d.ts} +3 -3
  103. package/dist/cli.d.ts +6 -6
  104. package/dist/cli.js +43 -42
  105. package/dist/compounding/engine.d.ts +1 -1
  106. package/dist/compounding/engine.js +8 -7
  107. package/dist/compounding/preference-consolidator.d.ts +1 -1
  108. package/dist/compression-optimizer.d.ts +1 -1
  109. package/dist/config.d.ts +1 -1
  110. package/dist/config.js +4 -3
  111. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  112. package/dist/connectors/codex-materialize-runner.js +8 -7
  113. package/dist/connectors/codex-materialize.d.ts +1 -1
  114. package/dist/connectors/index.d.ts +1 -1
  115. package/dist/connectors/index.js +9 -8
  116. package/dist/consolidation-provenance-check.d.ts +1 -1
  117. package/dist/consolidation-undo.d.ts +1 -1
  118. package/dist/contradiction/index.d.ts +1 -1
  119. package/dist/conversation-index/backend.d.ts +1 -1
  120. package/dist/conversation-index/chunker.d.ts +1 -1
  121. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  122. package/dist/conversation-index/indexer.d.ts +1 -1
  123. package/dist/conversation-index/search.d.ts +1 -1
  124. package/dist/day-summary.d.ts +1 -1
  125. package/dist/delinearize.d.ts +1 -1
  126. package/dist/direct-answer-wiring.d.ts +1 -1
  127. package/dist/direct-answer.d.ts +1 -1
  128. package/dist/embedding-fallback.d.ts +1 -1
  129. package/dist/enrichment/index.d.ts +1 -1
  130. package/dist/entity-retrieval.d.ts +1 -1
  131. package/dist/entity-retrieval.js +8 -7
  132. package/dist/entity-schema.d.ts +1 -1
  133. package/dist/explicit-capture.d.ts +4 -4
  134. package/dist/explicit-capture.js +3 -2
  135. package/dist/extraction-faithfulness.d.ts +1 -1
  136. package/dist/extraction-judge-telemetry.d.ts +1 -1
  137. package/dist/extraction-judge-training.d.ts +1 -1
  138. package/dist/extraction-judge.d.ts +1 -1
  139. package/dist/extraction.d.ts +1 -1
  140. package/dist/extraction.js +4 -3
  141. package/dist/fallback-llm.d.ts +1 -1
  142. package/dist/identity-continuity.d.ts +1 -1
  143. package/dist/importance.d.ts +1 -1
  144. package/dist/index.d.ts +9 -9
  145. package/dist/index.js +62 -61
  146. package/dist/index.js.map +1 -1
  147. package/dist/intent.d.ts +1 -1
  148. package/dist/lcm/engine.d.ts +1 -1
  149. package/dist/lcm/index.d.ts +1 -1
  150. package/dist/lcm/index.js +3 -3
  151. package/dist/lcm/tools.d.ts +1 -1
  152. package/dist/lifecycle.d.ts +1 -1
  153. package/dist/live-connectors-runner.d.ts +1 -1
  154. package/dist/local-llm.d.ts +1 -1
  155. package/dist/maintenance/memory-governance.d.ts +1 -1
  156. package/dist/maintenance/memory-governance.js +8 -7
  157. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -7
  158. package/dist/maintenance/rebuild-memory-projection.js +9 -8
  159. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  160. package/dist/memory-action-policy.d.ts +1 -1
  161. package/dist/memory-cache.d.ts +1 -1
  162. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  163. package/dist/memory-projection-store.d.ts +1 -1
  164. package/dist/memory-provenance.d.ts +1 -1
  165. package/dist/memory-worth-outcomes.d.ts +1 -1
  166. package/dist/models-json.d.ts +1 -1
  167. package/dist/namespaces/migrate.d.ts +2 -2
  168. package/dist/namespaces/migrate.js +12 -11
  169. package/dist/namespaces/principal.d.ts +1 -1
  170. package/dist/namespaces/search.d.ts +1 -1
  171. package/dist/namespaces/search.js +3 -3
  172. package/dist/namespaces/storage.d.ts +2 -2
  173. package/dist/namespaces/storage.js +8 -7
  174. package/dist/native-knowledge.d.ts +1 -1
  175. package/dist/operator-toolkit.d.ts +1 -1
  176. package/dist/operator-toolkit.js +17 -16
  177. package/dist/orchestration/maintenance.d.ts +2 -2
  178. package/dist/orchestration/maintenance.js +10 -9
  179. package/dist/{orchestrator-B1iVjEyc.d.ts → orchestrator-iVZWi8aW.d.ts} +21 -4
  180. package/dist/orchestrator.d.ts +4 -4
  181. package/dist/orchestrator.js +32 -31
  182. package/dist/patterns-cli.d.ts +1 -1
  183. package/dist/policy-runtime.d.ts +1 -1
  184. package/dist/provenance.d.ts +1 -1
  185. package/dist/provenance.js +3 -2
  186. package/dist/qmd-recall-cache.d.ts +1 -1
  187. package/dist/qmd.d.ts +1 -1
  188. package/dist/recall-disclosure-escalation.d.ts +1 -1
  189. package/dist/recall-explain-renderer.d.ts +1 -1
  190. package/dist/recall-explain-renderer.js +3 -3
  191. package/dist/recall-handles.d.ts +228 -0
  192. package/dist/recall-handles.js +42 -0
  193. package/dist/recall-handles.js.map +1 -0
  194. package/dist/recall-planner-llm.d.ts +1 -1
  195. package/dist/recall-state.d.ts +40 -2
  196. package/dist/recall-state.js +3 -1
  197. package/dist/recall-tag-filter.d.ts +1 -1
  198. package/dist/recall-xray-cli.d.ts +1 -1
  199. package/dist/recall-xray-cli.js +4 -4
  200. package/dist/recall-xray-renderer.d.ts +1 -1
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.d.ts +1 -1
  203. package/dist/recall-xray.js +2 -2
  204. package/dist/resolve-auth-token.d.ts +1 -1
  205. package/dist/resume-bundles.js +5 -4
  206. package/dist/retrieval-agents.d.ts +1 -1
  207. package/dist/retrieval-tiers.d.ts +1 -1
  208. package/dist/routing/engine.d.ts +1 -1
  209. package/dist/routing/store.d.ts +1 -1
  210. package/dist/sanitize.d.ts +2 -0
  211. package/dist/sanitize.js +6 -2
  212. package/dist/search/embed-helper.d.ts +1 -1
  213. package/dist/search/factory.d.ts +1 -1
  214. package/dist/search/factory.js +2 -2
  215. package/dist/search/index.d.ts +1 -1
  216. package/dist/search/index.js +4 -4
  217. package/dist/search/lancedb-backend.d.ts +1 -1
  218. package/dist/search/meilisearch-backend.d.ts +1 -1
  219. package/dist/search/noop-backend.d.ts +1 -1
  220. package/dist/search/orama-backend.d.ts +1 -1
  221. package/dist/search/port.d.ts +1 -1
  222. package/dist/search/remote-backend.d.ts +1 -1
  223. package/dist/{semantic-consolidation-CY8uVRNJ.d.ts → semantic-consolidation-DSjMDZ1u.d.ts} +1 -1
  224. package/dist/semantic-consolidation.d.ts +2 -2
  225. package/dist/semantic-consolidation.js +9 -8
  226. package/dist/semantic-rule-promotion.js +8 -7
  227. package/dist/semantic-rule-verifier.d.ts +1 -1
  228. package/dist/semantic-rule-verifier.js +8 -7
  229. package/dist/session-observer-bands.d.ts +1 -1
  230. package/dist/session-observer-state.d.ts +1 -1
  231. package/dist/shared-context/manager.d.ts +3 -3
  232. package/dist/signal.d.ts +1 -1
  233. package/dist/storage.d.ts +1 -1
  234. package/dist/storage.js +7 -6
  235. package/dist/summarizer.d.ts +1 -1
  236. package/dist/summary-snapshot.d.ts +1 -1
  237. package/dist/temporal-supersession.d.ts +1 -1
  238. package/dist/temporal-validity.d.ts +1 -1
  239. package/dist/threading.d.ts +1 -1
  240. package/dist/tier-migration.d.ts +1 -1
  241. package/dist/tier-routing.d.ts +1 -1
  242. package/dist/topics.d.ts +1 -1
  243. package/dist/transcript.d.ts +1 -1
  244. package/dist/transfer/import-sqlite.js +2 -2
  245. package/dist/{types-Dr0Cw3L9.d.ts → types-D9dwzU_x.d.ts} +25 -1
  246. package/dist/types.d.ts +1 -1
  247. package/dist/types.js +1 -1
  248. package/dist/utility-runtime.d.ts +1 -1
  249. package/dist/verified-recall.js +8 -7
  250. package/package.json +2 -2
  251. package/src/access-boundary.ts +4 -1
  252. package/src/access-http.ts +78 -20
  253. package/src/access-mcp.test.ts +29 -0
  254. package/src/access-mcp.ts +99 -17
  255. package/src/access-operations-batch.ts +14 -0
  256. package/src/access-operations.ts +5 -0
  257. package/src/access-schema.ts +8 -0
  258. package/src/access-service-observe-idempotency.test.ts +308 -0
  259. package/src/access-service.ts +106 -5
  260. package/src/access-surface-catalog.test.ts +4 -2
  261. package/src/access-surface-catalog.ts +5 -0
  262. package/src/active-memory-bridge.test.ts +72 -0
  263. package/src/active-memory-bridge.ts +21 -2
  264. package/src/chat/chat-cli.test.ts +142 -0
  265. package/src/chat/chat-cli.ts +206 -0
  266. package/src/chat/chat-config.ts +57 -0
  267. package/src/chat/chat-engine.test.ts +272 -0
  268. package/src/chat/chat-engine.ts +518 -0
  269. package/src/chat/chat-executor.ts +133 -0
  270. package/src/chat/chat-factory.ts +161 -0
  271. package/src/chat/chat-http.test.ts +197 -0
  272. package/src/chat/chat-http.ts +222 -0
  273. package/src/chat/chat-llm.ts +229 -0
  274. package/src/chat/chat-mcp.test.ts +135 -0
  275. package/src/chat/chat-security.test.ts +491 -0
  276. package/src/chat/chat-session.ts +296 -0
  277. package/src/chat/chat-system-prompt.ts +76 -0
  278. package/src/chat/chat-tools.ts +255 -0
  279. package/src/chat/chat-types.ts +167 -0
  280. package/src/cli.ts +20 -0
  281. package/src/config.ts +9 -0
  282. package/src/orchestrator.ts +106 -5
  283. package/src/recall-handles-wiring.test.ts +477 -0
  284. package/src/recall-handles.test.ts +283 -0
  285. package/src/recall-handles.ts +414 -0
  286. package/src/recall-state.ts +99 -0
  287. package/src/sanitize.ts +6 -0
  288. package/src/types.ts +29 -0
  289. package/dist/chunk-3XHD3XGK.js.map +0 -1
  290. package/dist/chunk-5KQCOIPW.js.map +0 -1
  291. package/dist/chunk-A2R5NV6O.js.map +0 -1
  292. package/dist/chunk-BOGENF7P.js.map +0 -1
  293. package/dist/chunk-GKPIY7EQ.js.map +0 -1
  294. package/dist/chunk-I3BT2IDW.js.map +0 -1
  295. package/dist/chunk-NUWZFSKC.js.map +0 -1
  296. package/dist/chunk-SINGJCUR.js.map +0 -1
  297. package/dist/chunk-Z3B2YW56.js.map +0 -1
  298. package/dist/chunk-ZVDRL6SK.js.map +0 -1
  299. /package/dist/{chunk-OMSTU233.js.map → chunk-2KAYTPPT.js.map} +0 -0
  300. /package/dist/{chunk-VYZNWH4J.js.map → chunk-2SXMCQEQ.js.map} +0 -0
  301. /package/dist/{chunk-NXL5CVE7.js.map → chunk-2Y6LMOQQ.js.map} +0 -0
  302. /package/dist/{chunk-LQ4J7ELC.js.map → chunk-67HG7KFE.js.map} +0 -0
  303. /package/dist/{chunk-ZBG4K36I.js.map → chunk-6FUJ2VYY.js.map} +0 -0
  304. /package/dist/{chunk-GMRNKPWO.js.map → chunk-6QUPUTT7.js.map} +0 -0
  305. /package/dist/{chunk-Z56IHRVV.js.map → chunk-6UU43ZGD.js.map} +0 -0
  306. /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
  307. /package/dist/{chunk-OWOKOFXK.js.map → chunk-AJOIM6UR.js.map} +0 -0
  308. /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
  309. /package/dist/{chunk-K22IJO4R.js.map → chunk-CTOQEZSN.js.map} +0 -0
  310. /package/dist/{chunk-HSDJCT3V.js.map → chunk-DXGS5YOQ.js.map} +0 -0
  311. /package/dist/{chunk-HV57RHMD.js.map → chunk-EH5ED3C6.js.map} +0 -0
  312. /package/dist/{chunk-7NDYFAJS.js.map → chunk-GM3BMWKR.js.map} +0 -0
  313. /package/dist/{chunk-5QUS24F7.js.map → chunk-I56LKMF7.js.map} +0 -0
  314. /package/dist/{chunk-35YJ6KCV.js.map → chunk-JEIROMLX.js.map} +0 -0
  315. /package/dist/{chunk-SKQCFAYU.js.map → chunk-JVEMUX6L.js.map} +0 -0
  316. /package/dist/{chunk-CXMXAC5R.js.map → chunk-K44MIN6D.js.map} +0 -0
  317. /package/dist/{chunk-BQCFXAMN.js.map → chunk-MPHDVHIB.js.map} +0 -0
  318. /package/dist/{chunk-QXNFQKWU.js.map → chunk-MTHNJCOJ.js.map} +0 -0
  319. /package/dist/{chunk-3TCRU4JA.js.map → chunk-O62P2LFL.js.map} +0 -0
  320. /package/dist/{chunk-JSDZMOT7.js.map → chunk-OC56UWV5.js.map} +0 -0
  321. /package/dist/{chunk-7IBEWQLG.js.map → chunk-QOIONSOI.js.map} +0 -0
  322. /package/dist/{chunk-4P5UAL3C.js.map → chunk-RBHANGVX.js.map} +0 -0
  323. /package/dist/{chunk-SK2CR6MW.js.map → chunk-RVLB4E6N.js.map} +0 -0
  324. /package/dist/{chunk-CNVIWMQI.js.map → chunk-UY4JMEAK.js.map} +0 -0
  325. /package/dist/{chunk-S6FQLQGH.js.map → chunk-V7GDTQ6I.js.map} +0 -0
  326. /package/dist/{chunk-JNOYYWCA.js.map → chunk-VO4UICII.js.map} +0 -0
  327. /package/dist/{chunk-54TI5GLV.js.map → chunk-WI4QX7GH.js.map} +0 -0
  328. /package/dist/{chunk-XKU4YE6Z.js.map → chunk-XZTUMWKQ.js.map} +0 -0
@@ -0,0 +1,477 @@
1
+ /**
2
+ * recall-handles-wiring.test.ts — PR2 integration tests for injection-time
3
+ * memory handles (issue #1582).
4
+ *
5
+ * Proves the wiring built on the pure recall-handles module (PR1):
6
+ * - the sanitizer surface (stripMemoryHandles) strips `[m:xxxx]` tokens;
7
+ * - the QMD injection formatter appends handles only when the gate is ON
8
+ * (gate-off is byte-identical to the pre-#1582 output — rule 39);
9
+ * - the shared resolver (Orchestrator.resolveMemoryIdOrHandle) returns the
10
+ * exact memory id for a cited handle, and throws on miss/ambiguity (rule
11
+ * 34/51) with snapshot depth respected;
12
+ * - the extraction buffer never holds handle text (hygiene §2 — rule 23);
13
+ * - consumers (memory_get, correction plan) resolve id-or-handle through the
14
+ * one shared access-service helper (rule 22).
15
+ *
16
+ * The full Done-When round-trip (recall injects `[m:xxxx]` → a later turn cites
17
+ * it → it resolves to the exact id → a correction targets that memory) is the
18
+ * last test in each section.
19
+ */
20
+
21
+ import assert from "node:assert/strict";
22
+ import os from "node:os";
23
+ import path from "node:path";
24
+ import { mkdtemp, rm, readdir, readFile, stat } from "node:fs/promises";
25
+ import test from "node:test";
26
+
27
+ import { parseConfig } from "./config.js";
28
+ import { Orchestrator } from "./orchestrator.js";
29
+ import { EngramAccessService, EngramAccessInputError } from "./access-service.js";
30
+ import { stripMemoryHandles } from "./sanitize.js";
31
+ import { handleFor, renderHandle } from "./recall-handles.js";
32
+ import type { PluginConfig, QmdSearchResult } from "./types.js";
33
+
34
+ // ─── helpers ──────────────────────────────────────────────────────────────
35
+
36
+ interface Harness {
37
+ orchestrator: Orchestrator;
38
+ service: EngramAccessService;
39
+ memoryDir: string;
40
+ }
41
+
42
+ async function makeHarness(
43
+ overrides: Partial<PluginConfig> = {},
44
+ ): Promise<Harness> {
45
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-handles-"));
46
+ const config = parseConfig({
47
+ openaiApiKey: "sk-test",
48
+ memoryDir,
49
+ workspaceDir: path.join(memoryDir, "workspace"),
50
+ // Keep the pipeline quiet: no QMD/graph/judge competing with the assertions.
51
+ qmdEnabled: false,
52
+ multiGraphMemoryEnabled: false,
53
+ entityGraphEnabled: false,
54
+ timeGraphEnabled: false,
55
+ causalGraphEnabled: false,
56
+ extractionJudgeEnabled: false,
57
+ temporalSupersessionEnabled: false,
58
+ contradictionDetectionEnabled: false,
59
+ chunkingEnabled: false,
60
+ embeddingFallbackEnabled: false,
61
+ extractionMinChars: 0,
62
+ extractionMinImportanceLevel: "trivial",
63
+ inlineSourceAttributionEnabled: false,
64
+ ...overrides,
65
+ });
66
+ const orchestrator = new Orchestrator(config);
67
+ const service = new EngramAccessService(orchestrator);
68
+ return { orchestrator, service, memoryDir };
69
+ }
70
+
71
+ /** QMD results carry `.path` ending in `<memoryId>.md`; that is the id handles derive from. */
72
+ function qmdResult(memoryId: string, snippet = "snippet", score = 0.9): QmdSearchResult {
73
+ return {
74
+ docid: memoryId,
75
+ path: `/mem/default/${memoryId}.md`,
76
+ line: 1,
77
+ snippet,
78
+ score,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Test-only view of the private QMD results formatter. `formatQmdResults` is
84
+ * private on the Orchestrator; this named interface is the single auditable
85
+ * unchecked assertion (rule: no inline cast for a property read).
86
+ */
87
+ interface FormatQmdResultsAccess {
88
+ formatQmdResults(
89
+ title: string,
90
+ results: QmdSearchResult[],
91
+ sessionKey?: string,
92
+ ): string;
93
+ }
94
+
95
+ function formatQmd(
96
+ orch: Orchestrator,
97
+ title: string,
98
+ results: QmdSearchResult[],
99
+ sessionKey?: string,
100
+ ): string {
101
+ const internals = orch as unknown as FormatQmdResultsAccess;
102
+ return internals.formatQmdResults(title, results, sessionKey);
103
+ }
104
+
105
+ const SESSION = "sess-handles-test";
106
+ const ID_A = "fact-1770469224307-eelr";
107
+ const ID_B = "fact-1770469224308-bxkq";
108
+
109
+ // ─── stripMemoryHandles (sanitizer surface) ─────────────────────────────────
110
+
111
+ test("stripMemoryHandles removes a trailing handle token and its preceding space", () => {
112
+ assert.equal(
113
+ stripMemoryHandles("API rate limit is 1000 rpm. [m:4f2a]"),
114
+ "API rate limit is 1000 rpm.",
115
+ );
116
+ });
117
+
118
+ test("stripMemoryHandles removes multiple inline handles and tidies spacing", () => {
119
+ assert.equal(
120
+ stripMemoryHandles("see [m:4f2a] and also [m:9b1c] for more"),
121
+ "see and also for more",
122
+ );
123
+ });
124
+
125
+ test("stripMemoryHandles leaves handle-free text unchanged", () => {
126
+ assert.equal(stripMemoryHandles("plain text, no handles"), "plain text, no handles");
127
+ });
128
+
129
+ // ─── formatQmdResults: gate-off characterization + gate-on rendering ────────
130
+
131
+ test("formatQmdResults: handles OFF emits no [m:xxxx] (byte-identical to pre-#1582)", async () => {
132
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: false });
133
+ try {
134
+ const out = formatQmd(orchestrator, "Workspace Context", [
135
+ qmdResult(ID_A, "The API rate limit is 1000 rpm."),
136
+ ]);
137
+ assert.doesNotMatch(out, /\[m:[0-9a-f]{4,8}\]/);
138
+ // The line layout is unchanged: numbered source line then snippet.
139
+ // source line carries the path + line number, e.g. `[1] /mem/.../fact-x.md:1 (score: 0.900)`.
140
+ assert.match(out, /\[1\] .*\.md.*\(score: /);
141
+ assert.match(out, /The API rate limit is 1000 rpm\./);
142
+ } finally {
143
+ await rm(memoryDir, { recursive: true, force: true });
144
+ }
145
+ });
146
+
147
+ test("formatQmdResults: handles ON appends [m:xxxx] derived from the memory id", async () => {
148
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
149
+ try {
150
+ const out = formatQmd(orchestrator, "Workspace Context", [
151
+ qmdResult(ID_A, "The API rate limit is 1000 rpm."),
152
+ ], SESSION);
153
+ const expected = `[m:${handleFor(ID_A)}]`;
154
+ assert.ok(
155
+ out.includes(expected),
156
+ `expected rendered handle ${expected} in output:\n${out}`,
157
+ );
158
+ // The handle lands at the snippet tail, not on the source/path line.
159
+ const esc = expected.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
160
+ assert.match(out, new RegExp(`The API rate limit is 1000 rpm\\. ${esc}`));
161
+ } finally {
162
+ await rm(memoryDir, { recursive: true, force: true });
163
+ }
164
+ });
165
+
166
+ test("formatQmdResults: result without an .md id gets no handle (entity reconstructions)", async () => {
167
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
168
+ try {
169
+ const out = formatQmd(orchestrator, "Workspace Context", [
170
+ { docid: "Widget", path: "/entities/Widget.json", line: 3, snippet: "entity reconstruction", score: 0.5 },
171
+ ], SESSION);
172
+ assert.doesNotMatch(out, /\[m:[0-9a-f]{4,8}\]/);
173
+ } finally {
174
+ await rm(memoryDir, { recursive: true, force: true });
175
+ }
176
+ });
177
+
178
+ test("formatQmdResults: a non-memory .md row (entity reconstruction) gets no handle (codex review)", async () => {
179
+ // Entity reconstructions can carry an `.md` path whose basename is a bare
180
+ // entity name (e.g. entities/Widget.md). Such a basename is not a loadable
181
+ // memory id, so it must NOT receive a handle — citing one would resolve to
182
+ // "Widget", which no storage can getMemoryById.
183
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
184
+ try {
185
+ const out = formatQmd(orchestrator, "Workspace Context", [
186
+ { docid: "Widget", path: "/mem/default/entities/Widget.md", line: 1, snippet: "entity reconstruction", score: 0.5 },
187
+ qmdResult(ID_A, "real memory"),
188
+ ], SESSION);
189
+ assert.doesNotMatch(out, /\[m:[0-9a-f]{4,8}\].*Widget|Widget.*\[m:/);
190
+ // The real memory still gets its handle.
191
+ assert.ok(out.includes(`[m:${handleFor(ID_A)}]`));
192
+ } finally {
193
+ await rm(memoryDir, { recursive: true, force: true });
194
+ }
195
+ });
196
+
197
+ test("formatQmdResults: handles OFF when sessionKey is absent even if config is on (cursor review)", async () => {
198
+ // A recall that injects context without a session key renders NO handles:
199
+ // the handle history is only recorded when sessionKey is present, so tokens
200
+ // shown without it could never be resolved (cursor review of #1582).
201
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
202
+ try {
203
+ const out = formatQmd(orchestrator, "Workspace Context", [
204
+ qmdResult(ID_A, "The API rate limit is 1000 rpm."),
205
+ ]);
206
+ assert.doesNotMatch(out, /\[m:[0-9a-f]{4,8}\]/);
207
+ } finally {
208
+ await rm(memoryDir, { recursive: true, force: true });
209
+ }
210
+ });
211
+
212
+ // ─── resolveMemoryIdOrHandle: the shared resolver every consumer calls ──────
213
+
214
+ test("resolveMemoryIdOrHandle: raw memory id passes through unchanged", async () => {
215
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
216
+ try {
217
+ assert.equal(orchestrator.resolveMemoryIdOrHandle(ID_A), ID_A);
218
+ } finally {
219
+ await rm(memoryDir, { recursive: true, force: true });
220
+ }
221
+ });
222
+
223
+ test("resolveMemoryIdOrHandle: handle resolves to the exact memory id after a recall record (round-trip)", async () => {
224
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
225
+ try {
226
+ await orchestrator.handleHistory.record(SESSION, [ID_A, ID_B]);
227
+ const token = renderHandle(ID_A); // [m:4f2a]
228
+ assert.equal(orchestrator.resolveMemoryIdOrHandle(token, SESSION), ID_A);
229
+ // bare hex form also resolves
230
+ assert.equal(
231
+ orchestrator.resolveMemoryIdOrHandle(handleFor(ID_A), SESSION),
232
+ ID_A,
233
+ );
234
+ } finally {
235
+ await rm(memoryDir, { recursive: true, force: true });
236
+ }
237
+ });
238
+
239
+ test("resolveMemoryIdOrHandle: unknown handle throws (rule 34 — never silent)", async () => {
240
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
241
+ try {
242
+ // No recorded history for this session → any handle misses.
243
+ assert.throws(
244
+ () => orchestrator.resolveMemoryIdOrHandle("[m:dead]", SESSION),
245
+ /not found in the recent recall history/i,
246
+ );
247
+ } finally {
248
+ await rm(memoryDir, { recursive: true, force: true });
249
+ }
250
+ });
251
+
252
+ test("resolveMemoryIdOrHandle: ambiguous handle throws listing candidates (rule 51)", async () => {
253
+ // Force a genuine width-4 collision: two distinct ids whose 4-hex handle
254
+ // matches. Birthday bound in a 65 536-space is ~300 samples, so a single
255
+ // linear pass with a Map finds one quickly (and deterministically for a fixed
256
+ // search prefix). In practice collisions across ~10–40 memories are
257
+ // vanishingly rare; this pins the disambiguation contract regardless.
258
+ const handles = new Map<string, string>();
259
+ let idA = "";
260
+ let idB = "";
261
+ for (let i = 0; i < 5000 && !idB; i++) {
262
+ const id = `fact-collide-${i}`;
263
+ const h = handleFor(id);
264
+ const existing = handles.get(h);
265
+ if (existing) {
266
+ idA = existing;
267
+ idB = id;
268
+ } else {
269
+ handles.set(h, id);
270
+ }
271
+ }
272
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
273
+ try {
274
+ if (!idB) {
275
+ // No collision found in the budget — skip rather than fake the scenario.
276
+ // The pure-module test (recall-handles.test.ts) covers the ambiguous
277
+ // branch with fixture snapshots directly.
278
+ assert.ok(true, "no width-4 collision found in synthetic search; ambiguous branch covered by PR1 unit tests");
279
+ return;
280
+ }
281
+ await orchestrator.handleHistory.record(SESSION, [idA, idB]);
282
+ assert.throws(
283
+ () => orchestrator.resolveMemoryIdOrHandle(renderHandle(idA), SESSION),
284
+ (err: Error) =>
285
+ /ambiguous/i.test(err.message) &&
286
+ err.message.includes(idA) &&
287
+ err.message.includes(idB),
288
+ );
289
+ } finally {
290
+ await rm(memoryDir, { recursive: true, force: true });
291
+ }
292
+ });
293
+
294
+ test("resolveMemoryIdOrHandle: snapshot depth respected — older-than-N is a miss", async () => {
295
+ const { orchestrator, memoryDir } = await makeHarness({
296
+ recallMemoryHandles: true,
297
+ recallHandleSnapshotDepth: 1,
298
+ });
299
+ try {
300
+ // Record two recalls; with depth=1 only the newest is searchable.
301
+ await orchestrator.handleHistory.record(SESSION, [ID_A]);
302
+ await orchestrator.handleHistory.record(SESSION, [ID_B]);
303
+ // ID_A is now outside the depth-1 window → its handle misses.
304
+ assert.throws(
305
+ () => orchestrator.resolveMemoryIdOrHandle(renderHandle(ID_A), SESSION),
306
+ /not found/i,
307
+ );
308
+ // ID_B is the newest → still resolves.
309
+ assert.equal(
310
+ orchestrator.resolveMemoryIdOrHandle(renderHandle(ID_B), SESSION),
311
+ ID_B,
312
+ );
313
+ } finally {
314
+ await rm(memoryDir, { recursive: true, force: true });
315
+ }
316
+ });
317
+
318
+ // ─── hygiene §2: handles never enter the extraction buffer ─────────────────
319
+
320
+ test("processTurn strips echoed handles from buffered content when the feature is ON", async () => {
321
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
322
+ try {
323
+ const handleToken = renderHandle(ID_A);
324
+ await orchestrator.processTurn("user", `That ${handleToken} is stale.`, SESSION);
325
+ const turns = orchestrator.buffer.getTurns(SESSION);
326
+ assert.ok(turns.length > 0, "turn was buffered");
327
+ const buffered = turns[turns.length - 1]!.content;
328
+ assert.doesNotMatch(buffered, /\[m:[0-9a-f]{4,8}\]/);
329
+ assert.ok(buffered.includes("stale"), "non-handle text is preserved");
330
+ } finally {
331
+ await rm(memoryDir, { recursive: true, force: true });
332
+ }
333
+ });
334
+
335
+ test("processTurn leaves content byte-identical when the feature is OFF", async () => {
336
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: false });
337
+ try {
338
+ // Even if a handle-shaped token appears (e.g. user typed it), the OFF path
339
+ // does not strip: nothing is injected, so buffering must be untouched.
340
+ const content = "see [m:4f2a] for context";
341
+ await orchestrator.processTurn("user", content, SESSION);
342
+ const turns = orchestrator.buffer.getTurns(SESSION);
343
+ assert.equal(turns[turns.length - 1]!.content, content);
344
+ } finally {
345
+ await rm(memoryDir, { recursive: true, force: true });
346
+ }
347
+ });
348
+
349
+ // ─── consumer wiring: memory_get + correction plan via the shared resolver ──
350
+
351
+ test("memoryGet resolves a handle to the underlying memory via sessionKey", async () => {
352
+ const { orchestrator, service, memoryDir } = await makeHarness({ recallMemoryHandles: true });
353
+ try {
354
+ // Seed a memory in the default namespace so getMemoryById finds it, then
355
+ // cite its handle. writeMemory returns the persisted id directly.
356
+ const defaultNs = orchestrator.config.defaultNamespace;
357
+ const storage = await orchestrator.getStorage(defaultNs);
358
+ const writtenId = await storage.writeMemory(
359
+ "fact",
360
+ "The API rate limit is 1000 rpm.",
361
+ { confidence: 0.9, source: "test" },
362
+ );
363
+ await orchestrator.handleHistory.record(SESSION, [writtenId]);
364
+
365
+ const res = await service.memoryGet(
366
+ renderHandle(writtenId),
367
+ undefined,
368
+ undefined,
369
+ SESSION,
370
+ );
371
+ assert.equal(res.found, true);
372
+ } finally {
373
+ await rm(memoryDir, { recursive: true, force: true });
374
+ }
375
+ });
376
+
377
+ test("correctionPlan: a handle in targetIds is resolved before planning; an unknown handle is an input error", async () => {
378
+ const { service, memoryDir } = await makeHarness({
379
+ recallMemoryHandles: true,
380
+ correctionEnabled: true,
381
+ });
382
+ try {
383
+ // No recorded recall history → any handle cited as a target misses, and the
384
+ // shared resolver surfaces it as an input error BEFORE the planner runs.
385
+ await assert.rejects(
386
+ () =>
387
+ service.correctionPlan({
388
+ text: "that memory is wrong",
389
+ targetIds: ["[m:dead]"],
390
+ sessionKey: SESSION,
391
+ }),
392
+ (err: unknown) =>
393
+ err instanceof EngramAccessInputError && /not found/i.test((err as Error).message),
394
+ );
395
+ } finally {
396
+ await rm(memoryDir, { recursive: true, force: true });
397
+ }
398
+ });
399
+
400
+ test("correctionPlan: a resolved handle targetIds set reaches the planner as concrete ids (round-trip)", async () => {
401
+ const { orchestrator, service, memoryDir } = await makeHarness({
402
+ recallMemoryHandles: true,
403
+ correctionEnabled: true,
404
+ });
405
+ try {
406
+ await orchestrator.handleHistory.record(SESSION, [ID_A]);
407
+ // The planner runs after resolution. With a known id it proceeds to
408
+ // search/locate; the contract we assert is that resolution did NOT throw
409
+ // (i.e. the handle was accepted and mapped). A downstream contract fault
410
+ // (empty candidate set / LLM unavailable) is out of scope here — the
411
+ // resolver's job is done once the handle maps to an id without error, which
412
+ // we prove by confirming no handle-resolution miss escapes.
413
+ try {
414
+ await service.correctionPlan({
415
+ text: "that memory is wrong",
416
+ targetIds: [renderHandle(ID_A)],
417
+ sessionKey: SESSION,
418
+ });
419
+ } catch (err) {
420
+ assert.ok(
421
+ !(err instanceof EngramAccessInputError && /not found|handle/i.test((err as Error).message)),
422
+ `handle resolution should have succeeded, got: ${(err as Error).message}`,
423
+ );
424
+ }
425
+ } finally {
426
+ await rm(memoryDir, { recursive: true, force: true });
427
+ }
428
+ });
429
+
430
+ // ─── corpus scan: no handle text in any persisted memory file ───────────────
431
+
432
+ async function scanTreeForHandles(dir: string): Promise<string[]> {
433
+ const hits: string[] = [];
434
+ let entries: string[];
435
+ try {
436
+ entries = await readdir(dir);
437
+ } catch {
438
+ return hits;
439
+ }
440
+ for (const entry of entries) {
441
+ const full = path.join(dir, entry);
442
+ let s;
443
+ try {
444
+ s = await stat(full);
445
+ } catch {
446
+ continue;
447
+ }
448
+ if (s.isDirectory()) {
449
+ hits.push(...(await scanTreeForHandles(full)));
450
+ } else if (entry.endsWith(".md")) {
451
+ const text = await readFile(full, "utf-8");
452
+ if (/\[m:[0-9a-f]{4,8}\]/.test(text)) hits.push(full);
453
+ }
454
+ }
455
+ return hits;
456
+ }
457
+
458
+ test("corpus scan: handles stripped before buffering never appear in persisted memory files", async () => {
459
+ const { orchestrator, memoryDir } = await makeHarness({ recallMemoryHandles: true });
460
+ try {
461
+ const handleToken = renderHandle(ID_A);
462
+ // Buffer several turns whose raw content echoes an injected handle.
463
+ await orchestrator.processTurn("user", `The limit is 1000 rpm. ${handleToken}`, SESSION);
464
+ await orchestrator.processTurn("assistant", `Got it, noting ${handleToken}.`, SESSION);
465
+ // Read the buffered turns directly and assert the strip held — the buffer is
466
+ // the extraction source, so if it is clean the persisted corpus is clean.
467
+ const turns = orchestrator.buffer.getTurns(SESSION);
468
+ for (const t of turns) {
469
+ assert.doesNotMatch(t.content, /\[m:[0-9a-f]{4,8}\]/);
470
+ }
471
+ // And confirm the on-disk memory tree has no handle text today.
472
+ const hits = await scanTreeForHandles(memoryDir);
473
+ assert.deepEqual(hits, [], `no persisted file should contain a handle token, found: ${hits.join(", ")}`);
474
+ } finally {
475
+ await rm(memoryDir, { recursive: true, force: true });
476
+ }
477
+ });