@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,491 @@
1
+ /**
2
+ * Chat security + correctness regression tests (issue #1583 review round).
3
+ *
4
+ * Covers the genuine HIGH-severity threads:
5
+ * - P1 path traversal: session id validation (chat-session.ts)
6
+ * - P1 stack-trace / raw-error exposure: replies never echo err.message
7
+ * - P2 confirmation bypass: memory_promote requires an affirmative confirm
8
+ * - HIGH intercepted apply fetches wrong plan: pending plan binds the REQUESTED id
9
+ * - HIGH pending-plan cleared wrongly: a normal turn must not clear an active plan
10
+ * - P1 tool-role normalization: production adapter converts role:"tool" → user
11
+ * - P2 deduplicate fenced tool-call candidates
12
+ * - P2 forward MCP scope: processChatMessage binds namespace/sessionKey
13
+ * - Medium recall_explain forwards query
14
+ */
15
+
16
+ import { strict as assert } from "node:assert";
17
+ import { mkdtemp, rm, readFile } from "node:fs/promises";
18
+ import { tmpdir } from "node:os";
19
+ import { join } from "node:path";
20
+ import { test } from "node:test";
21
+
22
+ import { ChatEngine } from "./chat-engine.js";
23
+ import { StubChatLlmAdapter } from "./chat-llm.js";
24
+ import { createProductionChatLlmAdapter, parseToolCalls } from "./chat-llm.js";
25
+ import {
26
+ createChatSession,
27
+ loadChatSession,
28
+ appendTranscriptEntry,
29
+ markPendingPlan,
30
+ chatSessionFile,
31
+ isSafeChatSessionId,
32
+ } from "./chat-session.js";
33
+ import { processChatMessage } from "./chat-factory.js";
34
+ import type { ChatToolExecutor } from "./chat-engine.js";
35
+ import type { EngramAccessService } from "../access-service.js";
36
+ import { createChatExecutor } from "./chat-executor.js";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Helpers
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function makeStubExecutor(overrides: Partial<ChatToolExecutor> = {}): ChatToolExecutor {
43
+ const base: ChatToolExecutor = {
44
+ async memorySearch() { return JSON.stringify({ results: [], count: 0 }); },
45
+ async memoryGet() { return JSON.stringify({ id: "abc" }); },
46
+ async memoryTimeline() { return JSON.stringify({ found: true, timeline: [] }); },
47
+ async recallExplain() { return JSON.stringify({ found: true }); },
48
+ async entityGet() { return JSON.stringify({ found: true }); },
49
+ async stats() { return JSON.stringify({ profile: "t" }); },
50
+ async reviewList() { return JSON.stringify({ items: [] }); },
51
+ async scopeInspect() { return JSON.stringify({ namespace: "default" }); },
52
+ async correctionPlan(request: string) { return { planId: `minted-${Date.now()}`, preview: `Plan for: ${request}` }; },
53
+ async correctionApply(planId: string) { return JSON.stringify({ applied: true, planId }); },
54
+ async memoryPromote(memoryId: string) { return JSON.stringify({ promoted: true, memoryId }); },
55
+ };
56
+ return { ...base, ...overrides };
57
+ }
58
+
59
+ function makeEngine(
60
+ llm: StubChatLlmAdapter,
61
+ executor: ChatToolExecutor,
62
+ opts: { correctionAvailable?: boolean } = {},
63
+ ): ChatEngine {
64
+ return new ChatEngine({
65
+ llm,
66
+ executor,
67
+ maxToolCallsPerTurn: 8,
68
+ correctionAvailable: opts.correctionAvailable ?? false,
69
+ scopeInspectAvailable: false,
70
+ });
71
+ }
72
+
73
+ async function makeTempDir(): Promise<string> {
74
+ return mkdtemp(join(tmpdir(), "chat-sec-"));
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // P1 — Path traversal (chat-session.ts)
79
+ // ---------------------------------------------------------------------------
80
+
81
+ test("path traversal: isSafeChatSessionId rejects traversal payloads", () => {
82
+ const malicious = [
83
+ "../../../../tmp/target",
84
+ "..\\..\\windows",
85
+ "../state/chat/neighbor",
86
+ "a/b/c",
87
+ "deep/../../escape",
88
+ "foo.bar",
89
+ ];
90
+ for (const id of malicious) {
91
+ assert.equal(isSafeChatSessionId(id), false, `must reject: ${id}`);
92
+ }
93
+ });
94
+
95
+ test("path traversal: isSafeChatSessionId accepts UUID-like ids", () => {
96
+ assert.ok(isSafeChatSessionId("a1b2c3d4-e5f6-7890-abcd-ef1234567890"));
97
+ assert.ok(isSafeChatSessionId("0123456789abcdef"));
98
+ });
99
+
100
+ test("path traversal: chatSessionFile throws on malicious id", () => {
101
+ assert.throws(() => chatSessionFile("/tmp/mem", "../../../../tmp/target"), /Invalid chat session id/);
102
+ });
103
+
104
+ test("path traversal: loadChatSession returns null for malicious id (no read outside chat dir)", async () => {
105
+ const dir = await makeTempDir();
106
+ try {
107
+ // chatSessionFile throws on the malicious id; loadChatSession catches that
108
+ // and returns null. The security property: NO file outside the chat dir is
109
+ // ever read — the caller sees "not found".
110
+ const result = await loadChatSession(dir, "../../../../tmp/target");
111
+ assert.equal(result, null, "malicious id must not read a file outside the chat dir");
112
+ } finally {
113
+ await rm(dir, { recursive: true, force: true });
114
+ }
115
+ });
116
+
117
+ test("path traversal: appendTranscriptEntry rejects malicious id (no write outside chat dir)", async () => {
118
+ const dir = await makeTempDir();
119
+ try {
120
+ await assert.rejects(() =>
121
+ appendTranscriptEntry(dir, "../../../../tmp/target2", { role: "user", content: "x" }),
122
+ );
123
+ } finally {
124
+ await rm(dir, { recursive: true, force: true });
125
+ }
126
+ });
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // P1 — Stack-trace / raw-error exposure (chat-engine.ts)
130
+ // ---------------------------------------------------------------------------
131
+
132
+ test("error sanitization: LLM failure reply does not echo raw err.message", async () => {
133
+ const executor = makeStubExecutor();
134
+ // Adapter that always throws — simulates an LLM transport error whose
135
+ // message might contain a stack/internal path.
136
+ const llm = {
137
+ async complete() { throw new Error("ECONNREFUSED 127.0.0.1:11434 / internal stack at foo.ts:42"); },
138
+ } as unknown as StubChatLlmAdapter;
139
+ const engine = makeEngine(llm, executor);
140
+ const session = await createChatSession(await makeTempDir(), {});
141
+ const result = await engine.processMessage("hi", session);
142
+
143
+ assert.ok(result.reply.includes("[error]"), "reply must be tagged as error");
144
+ assert.ok(
145
+ !result.reply.includes("ECONNREFUSED"),
146
+ `raw internal message must NOT leak into reply: ${result.reply}`,
147
+ );
148
+ assert.ok(
149
+ !result.reply.includes("internal stack"),
150
+ `stack-adjacent text must NOT leak into reply: ${result.reply}`,
151
+ );
152
+ });
153
+
154
+ test("error sanitization: correction-apply failure reply is generic", async () => {
155
+ const executor = makeStubExecutor({
156
+ async correctionApply() { throw new Error("secret internal: write failed at storage.ts:99"); },
157
+ });
158
+ // Step 1: confirm the plan (fast path) → triggers correctionApply → throws.
159
+ const llm = new StubChatLlmAdapter([{ content: "unused" }]);
160
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
161
+ const dir = await makeTempDir();
162
+ const session = await createChatSession(dir, {});
163
+ // Seed a pending plan so the confirmation fast-path runs correctionApply.
164
+ session.pendingPlanId = "plan-xyz";
165
+ const result = await engine.processMessage("apply", session);
166
+
167
+ assert.ok(result.reply.includes("[error]"), "reply must be tagged as error");
168
+ assert.ok(
169
+ !result.reply.includes("secret internal"),
170
+ `internal error detail must NOT leak: ${result.reply}`,
171
+ );
172
+ });
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // P2 — Confirmation bypass: memory_promote requires confirmation (Thread 9)
176
+ // ---------------------------------------------------------------------------
177
+
178
+ test("confirmation: memory_promote is intercepted and requires an affirmative confirm", async () => {
179
+ const promoteCalls: string[] = [];
180
+ const executor = makeStubExecutor({
181
+ async memoryPromote(memoryId: string) { promoteCalls.push(memoryId); return JSON.stringify({ promoted: true, memoryId }); },
182
+ });
183
+ // Step 1: LLM emits a memory_promote tool call.
184
+ const llm = new StubChatLlmAdapter([
185
+ { toolCalls: [{ name: "memory_promote", arguments: { memoryId: "mem-1" } }] },
186
+ { content: "done" },
187
+ ]);
188
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
189
+ const session = await createChatSession(await makeTempDir(), {});
190
+
191
+ const result = await engine.processMessage("promote mem-1", session);
192
+
193
+ // The promotion must NOT have executed yet (no confirmation received).
194
+ assert.equal(promoteCalls.length, 0, "memory_promote must not execute without confirmation");
195
+ assert.equal(session.pendingPromotionId, "mem-1", "pendingPromotionId must be recorded");
196
+ assert.ok(/apply/i.test(result.reply), "reply must ask the user to confirm");
197
+
198
+ // Now confirm → fast-path executes the promotion.
199
+ const result2 = await engine.processMessage("apply", session);
200
+ assert.equal(promoteCalls.length, 1, "memory_promote executes after confirmation");
201
+ assert.equal(promoteCalls[0], "mem-1");
202
+ assert.ok(/promoted/i.test(result2.reply), `reply should confirm promotion: ${result2.reply}`);
203
+ });
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // HIGH — Intercepted apply fetches wrong plan (Thread 11)
207
+ // ---------------------------------------------------------------------------
208
+
209
+ test("confirmation: pending plan binds the REQUESTED planId, not an executor re-minted id", async () => {
210
+ // The executor always returns a DIFFERENT planId than requested.
211
+ const executor = makeStubExecutor({
212
+ async correctionPlan() { return { planId: "MINTED-DIFFERENT", preview: "preview text" }; },
213
+ });
214
+ // Step 1: LLM emits correction_apply for planId "REQ-123" (unconfirmed).
215
+ const llm = new StubChatLlmAdapter([
216
+ { toolCalls: [{ name: "correction_apply", arguments: { planId: "REQ-123" } }] },
217
+ { content: "applied" },
218
+ ]);
219
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
220
+ const session = await createChatSession(await makeTempDir(), {});
221
+
222
+ const result = await engine.processMessage("apply this", session);
223
+
224
+ assert.equal(session.pendingPlanId, "REQ-123", "must bind the REQUESTED planId");
225
+ assert.equal(result.pendingPlan?.planId, "REQ-123", "result pendingPlan must be the requested id");
226
+ assert.notEqual(session.pendingPlanId, "MINTED-DIFFERENT", "must NOT use the executor re-minted id");
227
+ });
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // HIGH — Pending plan cleared wrongly (Thread 15)
231
+ // ---------------------------------------------------------------------------
232
+
233
+ test("pending-plan lifecycle: a normal turn does not clear an active pending plan", async () => {
234
+ const dir = await makeTempDir();
235
+ try {
236
+ const session0 = await createChatSession(dir, { principal: "alice" });
237
+ // Simulate a pending plan from an earlier correction-preview turn.
238
+ await markPendingPlan(dir, session0.id, "plan-active");
239
+
240
+ const fakeLlm = { chatCompletion: async () => ({ content: "just a normal answer" }) };
241
+ const service = {
242
+ fallbackLlmRef: fakeLlm,
243
+ localLlmRef: null,
244
+ memoryDir: dir,
245
+ configRef: {
246
+ chat: { enabled: true, model: "", maxToolCallsPerTurn: 8, sessionTtlHours: 72 },
247
+ },
248
+ } as unknown as EngramAccessService;
249
+
250
+ await processChatMessage({
251
+ service,
252
+ config: service.configRef!.chat,
253
+ memoryDir: dir,
254
+ message: "what is 2+2?",
255
+ chatSessionId: session0.id,
256
+ principal: "alice",
257
+ });
258
+
259
+ // Reload and verify the pending plan survived the normal turn.
260
+ const reloaded = await loadChatSession(dir, session0.id);
261
+ assert.equal(reloaded?.pendingPlanId, "plan-active", "normal turn must NOT clear the pending plan");
262
+ } finally {
263
+ await rm(dir, { recursive: true, force: true });
264
+ }
265
+ });
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // P1 — Tool-role normalization (chat-llm.ts, Thread 13)
269
+ // ---------------------------------------------------------------------------
270
+
271
+ test("tool-role normalization: production adapter converts role:tool to role:user", async () => {
272
+ const captured: Array<{ role: string; content: string }> = [];
273
+ const client = {
274
+ async chatCompletion(messages: Array<{ role: string; content: string }>) {
275
+ captured.push(...messages);
276
+ return { content: "final answer" };
277
+ },
278
+ };
279
+ const adapter = createProductionChatLlmAdapter(client);
280
+ await adapter.complete(
281
+ [
282
+ { role: "system", content: "sys" },
283
+ { role: "user", content: "q" },
284
+ { role: "assistant", content: "thinking" },
285
+ { role: "tool", content: '{"result":"ok"}', toolCallId: "call_1" },
286
+ ],
287
+ { tools: [] },
288
+ );
289
+
290
+ const roles = captured.map((m) => m.role);
291
+ assert.ok(!roles.includes("tool"), "no bare role:tool may reach the provider client");
292
+ const toolDerived = captured.find((m) => m.role === "user" && m.content.includes('{"result":"ok"}'));
293
+ assert.ok(toolDerived, "tool result must be surfaced as a user message");
294
+ assert.ok(toolDerived!.content.includes("call_1"), "toolCallId must be preserved in the content");
295
+ });
296
+
297
+ // ---------------------------------------------------------------------------
298
+ // P2 — Deduplicate fenced tool-call candidates (chat-llm.ts, Thread 18)
299
+ // ---------------------------------------------------------------------------
300
+
301
+ test("parseToolCalls: a fenced block is not duplicated by the bare-line scan", () => {
302
+ const content = [
303
+ "Let me search.",
304
+ "```json",
305
+ '{"tool": "memory_search", "arguments": {"query": "db"}}',
306
+ "```",
307
+ ].join("\n");
308
+ const calls = parseToolCalls(content);
309
+ assert.equal(calls.length, 1, `expected exactly one tool call, got ${calls.length}`);
310
+ assert.equal(calls[0].name, "memory_search");
311
+ });
312
+
313
+ test("parseToolCalls: distinct tool calls are all kept", () => {
314
+ const content = [
315
+ '{"tool": "memory_search", "arguments": {"query": "a"}}',
316
+ '{"tool": "memory_get", "arguments": {"memoryId": "x"}}',
317
+ ].join("\n");
318
+ const calls = parseToolCalls(content);
319
+ assert.equal(calls.length, 2);
320
+ });
321
+
322
+ // ---------------------------------------------------------------------------
323
+ // P2 — Forward MCP scope into new chat sessions (Thread 17)
324
+ // ---------------------------------------------------------------------------
325
+
326
+ test("scope forwarding: processChatMessage binds namespace/sessionKey into a new session", async () => {
327
+ const dir = await makeTempDir();
328
+ try {
329
+ const fakeLlm = { chatCompletion: async () => ({ content: "scoped reply" }) };
330
+ const service = {
331
+ fallbackLlmRef: fakeLlm,
332
+ localLlmRef: null,
333
+ memoryDir: dir,
334
+ configRef: {
335
+ chat: { enabled: true, model: "", maxToolCallsPerTurn: 8, sessionTtlHours: 72 },
336
+ },
337
+ } as unknown as EngramAccessService;
338
+
339
+ const result = await processChatMessage({
340
+ service,
341
+ config: service.configRef!.chat,
342
+ memoryDir: dir,
343
+ message: "hello",
344
+ principal: "alice",
345
+ namespace: "team-proj",
346
+ sessionKey: "sess-42",
347
+ });
348
+
349
+ const reloaded = await loadChatSession(dir, result.chatSessionId);
350
+ assert.equal(reloaded?.namespace, "team-proj", "new session must bind the forwarded namespace");
351
+ assert.equal(reloaded?.sessionKey, "sess-42", "new session must bind the forwarded sessionKey");
352
+
353
+ // Verify the system header carries the structured binding.
354
+ const raw = await readFile(chatSessionFile(dir, result.chatSessionId), "utf8");
355
+ const header = JSON.parse(raw.split("\n")[0]);
356
+ assert.equal(header.namespace, "team-proj");
357
+ assert.equal(header.sessionKey, "sess-42");
358
+ } finally {
359
+ await rm(dir, { recursive: true, force: true });
360
+ }
361
+ });
362
+
363
+ // ---------------------------------------------------------------------------
364
+ // Medium — recall_explain forwards query (Thread 1)
365
+ // ---------------------------------------------------------------------------
366
+
367
+ test("recall_explain: forwards the query argument to the executor", async () => {
368
+ let capturedQuery = "<not called>";
369
+ const executor = makeStubExecutor({
370
+ async recallExplain(query: string) { capturedQuery = query; return JSON.stringify({ found: true }); },
371
+ });
372
+ const llm = new StubChatLlmAdapter([
373
+ { toolCalls: [{ name: "recall_explain", arguments: { query: "why postgres" } }] },
374
+ { content: "because [id: abc]" },
375
+ ]);
376
+ const engine = makeEngine(llm, executor);
377
+ const session = await createChatSession(await makeTempDir(), {});
378
+ await engine.processMessage("why did you recall that?", session);
379
+ assert.equal(capturedQuery, "why postgres", "recall_explain must forward the query to the executor");
380
+ });
381
+
382
+ // ---------------------------------------------------------------------------
383
+ // P2 — Executor forwards maxResults=0 (Thread 14, kilo review)
384
+ // ---------------------------------------------------------------------------
385
+
386
+ test("executor: maxResults=0 is forwarded, not dropped by falsy check (Thread 14)", async () => {
387
+ let capturedArgs: Record<string, unknown> = {};
388
+ const mockService = {
389
+ briefingEnabled: false,
390
+ memorySearch: async (args: Record<string, unknown>) => {
391
+ capturedArgs = args;
392
+ return { results: [], count: 0 };
393
+ },
394
+ } as unknown as EngramAccessService;
395
+ const executor = createChatExecutor({
396
+ service: mockService,
397
+ principal: "test",
398
+ });
399
+ await executor.memorySearch("query", 0);
400
+ assert.equal(capturedArgs.maxResults, 0, "maxResults=0 must be forwarded, not dropped");
401
+ });
402
+
403
+ // ---------------------------------------------------------------------------
404
+ // HIGH — Promote bypass via repeated tool call (cursor OkuNd)
405
+ // ---------------------------------------------------------------------------
406
+
407
+ test("promote: a second tool call for the same memoryId does NOT bypass confirmation", async () => {
408
+ let promoteCalled = false;
409
+ const executor = makeStubExecutor({
410
+ async memoryPromote() { promoteCalled = true; return JSON.stringify({ promoted: true }); },
411
+ });
412
+ // correctionAvailable=true so memory_promote is in the schema and requires confirmation.
413
+ const llm = new StubChatLlmAdapter([
414
+ { toolCalls: [{ name: "memory_promote", arguments: { memoryId: "mem-1" } }] },
415
+ ]);
416
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
417
+ const session = await createChatSession(await makeTempDir(), {});
418
+
419
+ // Turn 1: LLM calls memory_promote — should be intercepted.
420
+ const result1 = await engine.processMessage("promote mem-1", session);
421
+ assert.ok(result1.reply.includes("apply"), "Turn 1 should ask for confirmation");
422
+ assert.equal(session.pendingPromotionId, "mem-1");
423
+ assert.equal(promoteCalled, false, "memory_promote must NOT execute in the tool loop");
424
+
425
+ // Turn 2: user types something that is NOT a confirmation keyword.
426
+ // The LLM calls memory_promote again. The engine must intercept again,
427
+ // NOT execute it (cursor HIGH: no bypass via repeated calls).
428
+ const llm2 = new StubChatLlmAdapter([
429
+ { toolCalls: [{ name: "memory_promote", arguments: { memoryId: "mem-1" } }] },
430
+ ]);
431
+ const engine2 = makeEngine(llm2, executor, { correctionAvailable: true });
432
+ const result2 = await engine2.processMessage("please promote it now", session);
433
+ assert.ok(result2.reply.includes("apply"), "Turn 2 should still ask for confirmation");
434
+ assert.equal(promoteCalled, false, "memory_promote must NOT execute without an exact confirm keyword");
435
+ });
436
+
437
+ // ---------------------------------------------------------------------------
438
+ // HIGH — Disabled mutating tools still execute (cursor OkuNk)
439
+ // ---------------------------------------------------------------------------
440
+
441
+ test("tool gate: memory_promote is rejected when correctionAvailable is false", async () => {
442
+ let promoteCalled = false;
443
+ const executor = makeStubExecutor({
444
+ async memoryPromote() { promoteCalled = true; return JSON.stringify({ promoted: true }); },
445
+ });
446
+ // correctionAvailable=false so memory_promote is NOT in the tool schema.
447
+ // The LLM hallucinates a memory_promote call anyway.
448
+ const llm = new StubChatLlmAdapter([
449
+ { toolCalls: [{ name: "memory_promote", arguments: { memoryId: "mem-1" } }] },
450
+ { content: "Done." },
451
+ ]);
452
+ const engine = makeEngine(llm, executor, { correctionAvailable: false });
453
+ const session = await createChatSession(await makeTempDir(), {});
454
+ await engine.processMessage("promote something", session);
455
+ assert.equal(promoteCalled, false, "memory_promote must NOT execute when not in the active schema set");
456
+ });
457
+
458
+ // ---------------------------------------------------------------------------
459
+ // HIGH — Failed apply restores pending state (cursor OlACo/OlACg)
460
+ // ---------------------------------------------------------------------------
461
+
462
+ test("failed correction apply restores pendingPlanId for retry (OlACo)", async () => {
463
+ const executor = makeStubExecutor({
464
+ async correctionApply() { throw new Error("disk full"); },
465
+ });
466
+ const llm = new StubChatLlmAdapter([]);
467
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
468
+ const session = await createChatSession(await makeTempDir(), {});
469
+ session.pendingPlanId = "plan-xyz";
470
+
471
+ const result = await engine.processMessage("apply", session);
472
+
473
+ assert.ok(result.reply.includes("[error]"), "Should return an error reply");
474
+ assert.equal(session.pendingPlanId, "plan-xyz", "pendingPlanId must be restored after failure");
475
+ assert.ok(!session.confirmedPlanIds.has("plan-xyz"), "confirmedPlanIds must not retain the failed planId");
476
+ });
477
+
478
+ test("failed promotion apply restores pendingPromotionId for retry (OlACo)", async () => {
479
+ const executor = makeStubExecutor({
480
+ async memoryPromote() { throw new Error("network error"); },
481
+ });
482
+ const llm = new StubChatLlmAdapter([]);
483
+ const engine = makeEngine(llm, executor, { correctionAvailable: true });
484
+ const session = await createChatSession(await makeTempDir(), {});
485
+ session.pendingPromotionId = "mem-abc";
486
+
487
+ const result = await engine.processMessage("apply", session);
488
+
489
+ assert.ok(result.reply.includes("[error]"), "Should return an error reply");
490
+ assert.equal(session.pendingPromotionId, "mem-abc", "pendingPromotionId must be restored after failure");
491
+ });