@remnic/core 9.3.613 → 9.3.615

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 (386) hide show
  1. package/dist/access-cli.js +59 -58
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +23 -23
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +20 -20
  7. package/dist/access-schema.d.ts +26 -14
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-CBNEKjzN.d.ts} +71 -6
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +17 -17
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-TH67Q46T.js → chunk-5OHHEORR.js} +64 -21
  48. package/dist/chunk-5OHHEORR.js.map +1 -0
  49. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  50. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  51. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  52. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  53. package/dist/chunk-6L46YAEZ.js.map +1 -0
  54. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  55. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  56. package/dist/chunk-AL4RAJL5.js.map +1 -0
  57. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  58. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  59. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  60. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  61. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  62. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  63. package/dist/chunk-C6C7XVKG.js.map +1 -0
  64. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  65. package/dist/chunk-CI7RKSRE.js.map +1 -0
  66. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  67. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  68. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  69. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  70. package/dist/chunk-DCGT4FPP.js.map +1 -0
  71. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  72. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  73. package/dist/chunk-DEVUWMME.js.map +1 -0
  74. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  75. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  76. package/dist/{chunk-HJNQQICM.js → chunk-EXUAP5LH.js} +108 -51
  77. package/dist/chunk-EXUAP5LH.js.map +1 -0
  78. package/dist/chunk-FAV25DUZ.js +12 -0
  79. package/dist/chunk-FAV25DUZ.js.map +1 -0
  80. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  81. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  82. package/dist/chunk-GDB4J2H3.js.map +1 -0
  83. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  84. package/dist/chunk-GLPBYIXN.js.map +1 -0
  85. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  86. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  87. package/dist/chunk-IBTZEBUD.js.map +1 -0
  88. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  89. package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
  90. package/dist/chunk-IMA6GU4Y.js.map +1 -0
  91. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  92. package/dist/chunk-JOASJWQR.js.map +1 -0
  93. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  94. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  95. package/dist/chunk-KBL3JJR6.js.map +1 -0
  96. package/dist/{chunk-IOTENEVL.js → chunk-KGLPJROV.js} +57 -50
  97. package/dist/chunk-KGLPJROV.js.map +1 -0
  98. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  99. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  100. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  101. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  102. package/dist/chunk-MHQC2WU2.js.map +1 -0
  103. package/dist/{chunk-5RPTH6AU.js → chunk-NM5NQYJE.js} +20 -19
  104. package/dist/chunk-NM5NQYJE.js.map +1 -0
  105. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  106. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  107. package/dist/chunk-ODPLEWB6.js.map +1 -0
  108. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  109. package/dist/chunk-OIF36KGD.js.map +1 -0
  110. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  111. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  112. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  113. package/dist/chunk-PYIFUBRK.js.map +1 -0
  114. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  115. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  116. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  117. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  118. package/dist/chunk-RG3LBSGH.js.map +1 -0
  119. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  120. package/dist/chunk-S53OYO3F.js.map +1 -0
  121. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  122. package/dist/chunk-SCPFRKIT.js.map +1 -0
  123. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  124. package/dist/chunk-T7N6KQGS.js.map +1 -0
  125. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  126. package/dist/chunk-TNOWU6RP.js.map +1 -0
  127. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  128. package/dist/chunk-TZVQQTG4.js.map +1 -0
  129. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  130. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-PPPZY2EU.js → chunk-WD2W4234.js} +9 -3
  134. package/dist/chunk-WD2W4234.js.map +1 -0
  135. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  136. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  137. package/dist/chunk-X6IRLNOO.js.map +1 -0
  138. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  139. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  140. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  141. package/dist/chunk-YRMKDTKF.js.map +1 -0
  142. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  143. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  144. package/dist/{chunk-2QANQKSQ.js → chunk-ZK32E74R.js} +156 -45
  145. package/dist/chunk-ZK32E74R.js.map +1 -0
  146. package/dist/{cli-OrfKXNU4.d.ts → cli-Cw729yLf.d.ts} +6 -2
  147. package/dist/cli.d.ts +3 -3
  148. package/dist/cli.js +61 -60
  149. package/dist/compounding/engine.js +3 -3
  150. package/dist/compounding/preference-consolidator.js +39 -11
  151. package/dist/compounding/preference-consolidator.js.map +1 -1
  152. package/dist/config.js +1 -1
  153. package/dist/connectors/codex-materialize-runner.js +3 -3
  154. package/dist/connectors/index.js +3 -3
  155. package/dist/consolidation-provenance-check.js +1 -1
  156. package/dist/contradiction/index.js +4 -4
  157. package/dist/conversation-index/backend.js +2 -2
  158. package/dist/conversation-index/indexer.js +1 -1
  159. package/dist/cross-namespace-budget.js +1 -1
  160. package/dist/enrichment/index.js +1 -1
  161. package/dist/entity-retrieval.js +3 -3
  162. package/dist/evals.js +1 -1
  163. package/dist/explicit-capture.d.ts +11 -1
  164. package/dist/explicit-capture.js +1 -1
  165. package/dist/extraction-judge.js +8 -1
  166. package/dist/extraction.js +2 -2
  167. package/dist/fallback-llm.d.ts +23 -6
  168. package/dist/fallback-llm.js +5 -3
  169. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  170. package/dist/index.d.ts +3 -3
  171. package/dist/index.js +95 -94
  172. package/dist/index.js.map +1 -1
  173. package/dist/lcm/archive.js +2 -2
  174. package/dist/lcm/engine.js +5 -5
  175. package/dist/lcm/index.js +7 -7
  176. package/dist/lcm/summarizer.js +3 -3
  177. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  178. package/dist/maintenance/memory-governance-cron.js +1 -1
  179. package/dist/maintenance/memory-governance.js +3 -3
  180. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  181. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  182. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  183. package/dist/mcp-memory-inspector-app.js +1 -1
  184. package/dist/migrate/from-engram.js +1 -1
  185. package/dist/namespaces/migrate.js +16 -15
  186. package/dist/namespaces/search.js +12 -11
  187. package/dist/namespaces/storage.js +3 -3
  188. package/dist/network/webdav.d.ts +2 -0
  189. package/dist/network/webdav.js +1 -1
  190. package/dist/objective-state-writers.js +2 -2
  191. package/dist/operator-toolkit.d.ts +3 -1
  192. package/dist/operator-toolkit.js +21 -20
  193. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  194. package/dist/orchestrator.d.ts +1 -1
  195. package/dist/orchestrator.js +48 -45
  196. package/dist/patterns-cli.js +1 -1
  197. package/dist/qmd-recall-cache.d.ts +2 -0
  198. package/dist/qmd-recall-cache.js +1 -1
  199. package/dist/qmd.d.ts +37 -2
  200. package/dist/qmd.js +4 -1
  201. package/dist/recall-explain-renderer.js +3 -3
  202. package/dist/recall-planner-llm.d.ts +57 -0
  203. package/dist/recall-planner-llm.js +167 -0
  204. package/dist/recall-planner-llm.js.map +1 -0
  205. package/dist/recall-xray-cli.js +4 -4
  206. package/dist/recall-xray-renderer.js +3 -3
  207. package/dist/recall-xray.js +2 -2
  208. package/dist/resume-bundles.js +2 -2
  209. package/dist/retrieval-agents.js +2 -2
  210. package/dist/routing/store.js +1 -1
  211. package/dist/schemas.d.ts +22 -22
  212. package/dist/search/factory.js +11 -10
  213. package/dist/search/index.js +11 -10
  214. package/dist/search/lancedb-backend.d.ts +1 -1
  215. package/dist/search/lancedb-backend.js +3 -2
  216. package/dist/search/meilisearch-backend.d.ts +1 -1
  217. package/dist/search/meilisearch-backend.js +3 -2
  218. package/dist/search/noop-backend.d.ts +1 -1
  219. package/dist/search/noop-backend.js +1 -1
  220. package/dist/search/orama-backend.d.ts +1 -1
  221. package/dist/search/orama-backend.js +3 -2
  222. package/dist/search/port.d.ts +6 -1
  223. package/dist/search/port.js +7 -0
  224. package/dist/search/remote-backend.d.ts +1 -1
  225. package/dist/search/remote-backend.js +1 -1
  226. package/dist/semantic-consolidation.js +4 -4
  227. package/dist/semantic-rule-promotion.js +3 -3
  228. package/dist/semantic-rule-verifier.js +3 -3
  229. package/dist/session-observer-state.js +1 -1
  230. package/dist/storage.js +2 -2
  231. package/dist/summarizer.js +2 -2
  232. package/dist/temporal-index.js +1 -1
  233. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  234. package/dist/transfer/backup.js +2 -2
  235. package/dist/transfer/capsule-export.js +2 -2
  236. package/dist/transfer/capsule-import.js +2 -2
  237. package/dist/transfer/export-sqlite.js +1 -1
  238. package/dist/transfer/types.d.ts +12 -12
  239. package/dist/types.d.ts +32 -0
  240. package/dist/types.js +1 -1
  241. package/dist/utility-learner.js +1 -1
  242. package/dist/utility-runtime.js +2 -2
  243. package/dist/verified-recall.js +3 -3
  244. package/dist/work/board.js +2 -2
  245. package/dist/work/storage.d.ts +2 -0
  246. package/dist/work/storage.js +1 -1
  247. package/package.json +1 -1
  248. package/src/access-http.ts +24 -10
  249. package/src/access-mcp.test.ts +160 -0
  250. package/src/access-mcp.ts +72 -7
  251. package/src/access-schema.ts +11 -0
  252. package/src/access-service-coding-write.test.ts +478 -0
  253. package/src/access-service.ts +237 -32
  254. package/src/active-recall.test.ts +40 -0
  255. package/src/active-recall.ts +19 -2
  256. package/src/behavior-learner.ts +5 -3
  257. package/src/buffer-session.test.ts +58 -0
  258. package/src/buffer-surprise-trigger.test.ts +4 -18
  259. package/src/buffer.ts +39 -11
  260. package/src/calibration.ts +10 -4
  261. package/src/causal-consolidation.test.ts +47 -2
  262. package/src/causal-consolidation.ts +13 -9
  263. package/src/cli.ts +19 -4
  264. package/src/compounding/engine.ts +2 -0
  265. package/src/compounding/preference-consolidator.test.ts +292 -0
  266. package/src/compounding/preference-consolidator.ts +55 -19
  267. package/src/config.test.ts +213 -0
  268. package/src/config.ts +175 -4
  269. package/src/connectors/codex-materialize-runner.ts +7 -4
  270. package/src/consolidation-provenance-check.ts +24 -5
  271. package/src/conversation-index/indexer.test.ts +22 -0
  272. package/src/conversation-index/indexer.ts +7 -3
  273. package/src/cross-namespace-budget.test.ts +44 -21
  274. package/src/cross-namespace-budget.ts +2 -2
  275. package/src/enrichment/pipeline.ts +11 -16
  276. package/src/evals.ts +1 -1
  277. package/src/explicit-capture.ts +19 -2
  278. package/src/extraction-judge-chain.test.ts +55 -0
  279. package/src/extraction-judge.ts +7 -9
  280. package/src/extraction.ts +16 -5
  281. package/src/fallback-llm.test.ts +600 -1
  282. package/src/fallback-llm.ts +91 -22
  283. package/src/maintenance/memory-governance-cron.ts +39 -29
  284. package/src/mcp-memory-inspector-app.ts +54 -12
  285. package/src/message-parts/index.ts +6 -0
  286. package/src/message-parts/message-parts.test.ts +30 -0
  287. package/src/migrate/from-engram.ts +19 -5
  288. package/src/namespaces/search.test.ts +15 -2
  289. package/src/namespaces/search.ts +1 -1
  290. package/src/network/webdav.ts +61 -21
  291. package/src/operator-toolkit.ts +6 -2
  292. package/src/orchestrator.ts +173 -20
  293. package/src/qmd-client.test.ts +85 -0
  294. package/src/qmd-recall-cache.test.ts +16 -0
  295. package/src/qmd-recall-cache.ts +7 -0
  296. package/src/qmd.test.ts +54 -0
  297. package/src/qmd.ts +119 -19
  298. package/src/recall-planner-llm.test.ts +224 -0
  299. package/src/recall-planner-llm.ts +289 -0
  300. package/src/routing/store.ts +4 -8
  301. package/src/search/factory.ts +3 -0
  302. package/src/search/lancedb-backend.ts +15 -3
  303. package/src/search/meilisearch-backend.ts +70 -7
  304. package/src/search/noop-backend.ts +5 -1
  305. package/src/search/orama-backend.ts +15 -3
  306. package/src/search/port.ts +15 -0
  307. package/src/search/remote-backend.ts +5 -1
  308. package/src/session-observer-state.ts +1 -1
  309. package/src/summarizer.ts +3 -3
  310. package/src/temporal-index.test.ts +18 -0
  311. package/src/temporal-index.ts +45 -0
  312. package/src/training-export/cli-date-validation.test.ts +36 -0
  313. package/src/training-export/date-parse.ts +21 -2
  314. package/src/transfer/export-sqlite.ts +3 -0
  315. package/src/types.ts +35 -0
  316. package/src/utility-learner.ts +1 -0
  317. package/src/work/storage.ts +23 -0
  318. package/dist/chunk-2QANQKSQ.js.map +0 -1
  319. package/dist/chunk-5RPTH6AU.js.map +0 -1
  320. package/dist/chunk-AJA46VX5.js.map +0 -1
  321. package/dist/chunk-C4SQJZAF.js.map +0 -1
  322. package/dist/chunk-CHCA44C3.js.map +0 -1
  323. package/dist/chunk-CSKLPDN6.js.map +0 -1
  324. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  325. package/dist/chunk-EAZGEEG2.js.map +0 -1
  326. package/dist/chunk-EUML3N6B.js.map +0 -1
  327. package/dist/chunk-EVZFIAPG.js.map +0 -1
  328. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  329. package/dist/chunk-GMAG2HS4.js.map +0 -1
  330. package/dist/chunk-HENLZHIT.js.map +0 -1
  331. package/dist/chunk-HINSGUA7.js.map +0 -1
  332. package/dist/chunk-HJNQQICM.js.map +0 -1
  333. package/dist/chunk-HPWVAEET.js.map +0 -1
  334. package/dist/chunk-IOTENEVL.js.map +0 -1
  335. package/dist/chunk-IP73YCZP.js.map +0 -1
  336. package/dist/chunk-JHMFYY7L.js.map +0 -1
  337. package/dist/chunk-JNANKJLN.js.map +0 -1
  338. package/dist/chunk-KGK2QKWL.js.map +0 -1
  339. package/dist/chunk-KM2A35EO.js.map +0 -1
  340. package/dist/chunk-KVEVLBKC.js.map +0 -1
  341. package/dist/chunk-L227SKTB.js.map +0 -1
  342. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  343. package/dist/chunk-NOMEVTUD.js.map +0 -1
  344. package/dist/chunk-NZPF2SYV.js.map +0 -1
  345. package/dist/chunk-PCI747N2.js.map +0 -1
  346. package/dist/chunk-PPPZY2EU.js.map +0 -1
  347. package/dist/chunk-TH67Q46T.js.map +0 -1
  348. package/dist/chunk-UWY7GIVS.js.map +0 -1
  349. package/dist/chunk-VJXSUAO7.js.map +0 -1
  350. package/dist/chunk-XKIQZXUB.js.map +0 -1
  351. package/dist/chunk-XPSVGJYA.js.map +0 -1
  352. package/dist/chunk-XSWKORGM.js.map +0 -1
  353. package/dist/chunk-YCN4BVDK.js.map +0 -1
  354. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  355. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  356. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  357. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  358. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  359. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  360. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  361. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  362. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  363. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  364. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  365. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  366. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  367. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  368. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  369. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  370. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  371. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  372. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  373. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  374. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  375. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  376. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  377. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  378. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  379. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  380. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  381. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  382. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  383. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  384. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  385. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  386. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
package/src/access-mcp.ts CHANGED
@@ -81,11 +81,15 @@ function toLegacyToolName(name: string): string {
81
81
  : name;
82
82
  }
83
83
 
84
- function withToolAliases(tool: McpTool): McpTool[] {
84
+ function withToolAliases(tool: McpTool, emitLegacyTools = true): McpTool[] {
85
85
  const canonicalName = toCanonicalToolName(tool.name);
86
86
  const canonicalTool = canonicalName === tool.name ? tool : { ...tool, name: canonicalName };
87
87
  if (canonicalName === tool.name) return [canonicalTool];
88
- return [canonicalTool, tool];
88
+ // Issue #1427: when legacy aliases are opted out, advertise only the
89
+ // canonical `remnic.*` name and drop the `engram.*` duplicate. Tool *calls*
90
+ // still accept both names (the dispatch canonicalizes), so suppressing the
91
+ // alias only trims `tools/list`, not callability.
92
+ return emitLegacyTools ? [canonicalTool, tool] : [canonicalTool];
89
93
  }
90
94
 
91
95
  function resolveChatGptInspectorRecallSessionKey(
@@ -111,6 +115,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
111
115
  "entityRef",
112
116
  "ttl",
113
117
  "sourceReason",
118
+ "cwd",
119
+ "projectTag",
114
120
  ],
115
121
  suggestionSubmit: [
116
122
  "schemaVersion",
@@ -125,6 +131,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
125
131
  "entityRef",
126
132
  "ttl",
127
133
  "sourceReason",
134
+ "cwd",
135
+ "projectTag",
128
136
  ],
129
137
  capsuleExport: [
130
138
  "name",
@@ -134,9 +142,40 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
134
142
  "peerIds",
135
143
  "includeTranscripts",
136
144
  "encrypt",
145
+ "cwd",
146
+ "projectTag",
137
147
  ],
138
- capsuleImport: ["archivePath", "namespace", "mode", "passphrase"],
139
- capsuleList: ["namespace"],
148
+ capsuleImport: ["archivePath", "namespace", "mode", "passphrase", "cwd", "projectTag"],
149
+ capsuleList: ["namespace", "cwd", "projectTag"],
150
+ };
151
+
152
+ // Shared JSON-schema fragments for the client-injected git/project context
153
+ // fields (#1434). Declared once to avoid drift across tool definitions.
154
+ // `_SCOPED` is for write tools that resolve a project namespace from these
155
+ // fields; `_IGNORED` is for tools that merely tolerate them for MCP client
156
+ // compatibility (clients like Pi MCPorter auto-inject `cwd` on every call).
157
+ const MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED: Record<string, unknown> = {
158
+ cwd: {
159
+ type: "string",
160
+ description:
161
+ "Optional working directory. When no explicit namespace is given, resolves the project namespace this write is stored in (mirrors recall/observe).",
162
+ },
163
+ projectTag: {
164
+ type: "string",
165
+ description:
166
+ "Optional project tag for non-git project scoping. When no explicit namespace is given, routes this write to the tagged project namespace.",
167
+ },
168
+ };
169
+ const MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED: Record<string, unknown> = {
170
+ cwd: {
171
+ type: "string",
172
+ description:
173
+ "Accepted for MCP client compatibility (git-context auto-injection); ignored by this tool.",
174
+ },
175
+ projectTag: {
176
+ type: "string",
177
+ description: "Accepted for MCP client compatibility; ignored by this tool.",
178
+ },
140
179
  };
141
180
 
142
181
  function parseMcpRequest<N extends SchemaName>(
@@ -212,13 +251,25 @@ export class EngramMcpServer {
212
251
  private readonly citationsEnabled: boolean;
213
252
  /** Whether to auto-enable citations for Codex adapter connections. */
214
253
  private readonly citationsAutoDetect: boolean;
254
+ /**
255
+ * Whether to advertise legacy `engram.*` tool aliases alongside the canonical
256
+ * `remnic.*` names (issue #1427). Defaults to true for backward compatibility;
257
+ * set false to halve the advertised `tools/list` surface.
258
+ */
259
+ private readonly emitLegacyTools: boolean;
215
260
 
216
261
  constructor(
217
262
  private readonly service: EngramAccessService,
218
- options: { principal?: string; citationsEnabled?: boolean; citationsAutoDetect?: boolean } = {},
263
+ options: {
264
+ principal?: string;
265
+ citationsEnabled?: boolean;
266
+ citationsAutoDetect?: boolean;
267
+ emitLegacyTools?: boolean;
268
+ } = {},
219
269
  ) {
220
270
  this.citationsEnabled = options.citationsEnabled === true;
221
271
  this.citationsAutoDetect = options.citationsAutoDetect !== false;
272
+ this.emitLegacyTools = options.emitLegacyTools !== false;
222
273
  this.authenticatedPrincipal =
223
274
  options.principal?.trim() ||
224
275
  readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() ||
@@ -598,6 +649,7 @@ export class EngramMcpServer {
598
649
  },
599
650
  includeTranscripts: { type: "boolean" },
600
651
  encrypt: { type: "boolean" },
652
+ ...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
601
653
  },
602
654
  required: ["name"],
603
655
  additionalProperties: false,
@@ -623,6 +675,7 @@ export class EngramMcpServer {
623
675
  type: "string",
624
676
  description: "Passphrase for encrypted capsule archives.",
625
677
  },
678
+ ...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
626
679
  },
627
680
  required: ["archivePath"],
628
681
  additionalProperties: false,
@@ -635,6 +688,7 @@ export class EngramMcpServer {
635
688
  type: "object",
636
689
  properties: {
637
690
  namespace: { type: "string" },
691
+ ...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
638
692
  },
639
693
  additionalProperties: false,
640
694
  },
@@ -739,6 +793,7 @@ export class EngramMcpServer {
739
793
  entityRef: { type: "string" },
740
794
  ttl: { type: "string" },
741
795
  sourceReason: { type: "string" },
796
+ ...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
742
797
  },
743
798
  required: ["content"],
744
799
  additionalProperties: false,
@@ -762,6 +817,7 @@ export class EngramMcpServer {
762
817
  entityRef: { type: "string" },
763
818
  ttl: { type: "string" },
764
819
  sourceReason: { type: "string" },
820
+ ...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
765
821
  },
766
822
  required: ["content"],
767
823
  additionalProperties: false,
@@ -1703,7 +1759,7 @@ export class EngramMcpServer {
1703
1759
  additionalProperties: false,
1704
1760
  },
1705
1761
  },
1706
- ].flatMap((tool) => withToolAliases(tool));
1762
+ ].flatMap((tool) => withToolAliases(tool, this.emitLegacyTools));
1707
1763
  }
1708
1764
 
1709
1765
  /** Get clientInfo for a specific MCP session. Returns undefined for non-MCP requests. */
@@ -1985,7 +2041,12 @@ export class EngramMcpServer {
1985
2041
  }
1986
2042
 
1987
2043
  private toolAcceptsArgument(name: string, key: string): boolean {
1988
- const tool = this.tools.find((entry) => entry.name === name);
2044
+ // Match by canonical name so argument validation resolves whether the
2045
+ // caller used the `engram.*` or `remnic.*` name and regardless of whether
2046
+ // legacy aliases are advertised (issue #1427) — a tool stays callable under
2047
+ // both names even when only the canonical alias appears in `tools/list`.
2048
+ const target = toCanonicalToolName(name);
2049
+ const tool = this.tools.find((entry) => toCanonicalToolName(entry.name) === target);
1989
2050
  const inputSchema = getObjectProperties(tool?.inputSchema);
1990
2051
  const properties = getObjectProperties(inputSchema?.properties);
1991
2052
  if (properties && Object.prototype.hasOwnProperty.call(properties, key)) {
@@ -2481,6 +2542,8 @@ export class EngramMcpServer {
2481
2542
  entityRef: body.entityRef,
2482
2543
  ttl: body.ttl,
2483
2544
  sourceReason: body.sourceReason,
2545
+ cwd: body.cwd,
2546
+ projectTag: body.projectTag,
2484
2547
  });
2485
2548
  }
2486
2549
  case "engram.suggestion_submit": {
@@ -2499,6 +2562,8 @@ export class EngramMcpServer {
2499
2562
  entityRef: body.entityRef,
2500
2563
  ttl: body.ttl,
2501
2564
  sourceReason: body.sourceReason,
2565
+ cwd: body.cwd,
2566
+ projectTag: body.projectTag,
2502
2567
  });
2503
2568
  }
2504
2569
  case "engram.entity_get":
@@ -243,6 +243,17 @@ export const memoryStoreRequestSchema = z.object({
243
243
  entityRef: entityRefSchema,
244
244
  ttl: ttlSchema,
245
245
  sourceReason: sourceReasonSchema,
246
+ // Git/project context for project-scoped writes (#1434). When no explicit
247
+ // `namespace` is given, these route the write to the same project namespace
248
+ // recall/observe resolve from `cwd`/`projectTag` (issue #569, rule 42). Also
249
+ // lets MCP clients that auto-inject `cwd` (e.g. Pi MCPorter) call write tools.
250
+ cwd: z.string().trim().min(1, "cwd must be non-empty when provided").max(2048).optional(),
251
+ projectTag: z
252
+ .string()
253
+ .trim()
254
+ .min(1, "projectTag must be non-empty when provided")
255
+ .max(256)
256
+ .optional(),
246
257
  });
247
258
 
248
259
  export const suggestionSubmitRequestSchema = memoryStoreRequestSchema;
@@ -0,0 +1,478 @@
1
+ /**
2
+ * #1434: explicit-write tools (memory_store / suggestion_submit) must resolve
3
+ * their write namespace through the SAME project-scope overlay the read path
4
+ * uses, so a memory stored with a client-injected `cwd`/`projectTag` is
5
+ * discoverable by project-scoped recall (rule 42 symmetry). Previously these
6
+ * tools ignored coding context and always wrote to the base namespace.
7
+ *
8
+ * Invariants verified here (review hardening on PR #1444):
9
+ * - Symmetry: a `projectTag`/`cwd` (or an existing session context) overlays
10
+ * the project namespace onto the principal self base — the SAME namespace
11
+ * recall/observe/buffer use — so scoped stores are found by scoped recall.
12
+ * - Base: the principal self namespace (defaultNamespaceForPrincipal), which
13
+ * collapses to `config.defaultNamespace` when namespaces are disabled or the
14
+ * principal has no self policy (the common deployment is unchanged).
15
+ * - Read-only: the resolver NEVER mutates session coding context, so
16
+ * idempotency peeks / dryRun preflights are side-effect free (Codex review).
17
+ * - Persist: a pre-resolved project namespace reaches storage instead of being
18
+ * rejected by the static policy allow-list (Codex P1 / Cursor High).
19
+ * - Precedence: explicit `namespace` wins; namespaces-disabled is a no-op.
20
+ */
21
+ import assert from "node:assert/strict";
22
+ import test from "node:test";
23
+
24
+ import { EngramAccessService } from "./access-service.js";
25
+ import { Orchestrator } from "./orchestrator.js";
26
+ import { persistExplicitCapture } from "./explicit-capture.js";
27
+ import type { ValidExplicitCapture } from "./explicit-capture.js";
28
+ import {
29
+ combineNamespaces,
30
+ projectNamespaceName,
31
+ projectTagProjectId,
32
+ } from "./coding/coding-namespace.js";
33
+ import type { CodingContext, PluginConfig } from "./types.js";
34
+
35
+ function makeOrchestratorStub(overrides: Partial<PluginConfig> = {}): Orchestrator {
36
+ const orch = Object.create(Orchestrator.prototype) as Orchestrator;
37
+ const internals = orch as unknown as {
38
+ config: PluginConfig;
39
+ _codingContextBySession: Map<string, CodingContext>;
40
+ };
41
+ internals.config = {
42
+ namespacesEnabled: true,
43
+ defaultNamespace: "default",
44
+ sharedNamespace: "shared",
45
+ namespacePolicies: [],
46
+ codingMode: { projectScope: true },
47
+ memoryDir: "/synthetic/remnic-coding-write",
48
+ recallCrossNamespaceBudgetEnabled: false,
49
+ recallCrossNamespaceBudgetWindowMs: 60_000,
50
+ recallCrossNamespaceBudgetSoftLimit: 10,
51
+ recallCrossNamespaceBudgetHardLimit: 30,
52
+ ...overrides,
53
+ } as unknown as PluginConfig;
54
+ internals._codingContextBySession = new Map();
55
+ return orch;
56
+ }
57
+
58
+ function resolver(service: EngramAccessService) {
59
+ return (req: unknown) =>
60
+ (
61
+ service as unknown as {
62
+ resolveCodingScopedWriteNamespace: (r: unknown) => Promise<string>;
63
+ }
64
+ ).resolveCodingScopedWriteNamespace(req);
65
+ }
66
+
67
+ function projectNamespaceFor(tag: string): string {
68
+ // projectScope (no branch scope) overlay namespace == projectNamespaceName.
69
+ return combineNamespaces("default", projectNamespaceName(projectTagProjectId(tag)));
70
+ }
71
+
72
+ test("#1434 projectTag scopes the write to the project namespace, read-only", async () => {
73
+ const orch = makeOrchestratorStub();
74
+ const service = new EngramAccessService(orch);
75
+
76
+ const resolved = await resolver(service)({
77
+ sessionKey: "sess-1",
78
+ authenticatedPrincipal: "alice",
79
+ projectTag: "Blend/Supply",
80
+ content: "x",
81
+ });
82
+
83
+ assert.equal(resolved, projectNamespaceFor("Blend/Supply"));
84
+ assert.notEqual(resolved, "default", "project context must change the namespace");
85
+ // Read-only: resolving must NOT persist coding context on the session.
86
+ assert.equal(
87
+ orch.getCodingContextForSession("sess-1"),
88
+ null,
89
+ "resolver must not mutate session coding context (peek/dryRun safety)",
90
+ );
91
+ });
92
+
93
+ test("#1434 a sessionless write with projectTag stays on the base namespace (recall symmetry)", async () => {
94
+ // Without a sessionKey the recall path can't attach or look up coding context
95
+ // (maybeAttachCodingContext / applyCodingNamespaceOverlay both no-op), so a
96
+ // sessionless recall searches the base namespace. A sessionless write must
97
+ // therefore also stay on the base — else the store would be hidden from the
98
+ // same client's recall (Codex review).
99
+ const orch = makeOrchestratorStub();
100
+ const service = new EngramAccessService(orch);
101
+ const resolved = await resolver(service)({
102
+ authenticatedPrincipal: "alice",
103
+ projectTag: "Blend/Supply",
104
+ content: "x",
105
+ });
106
+ assert.equal(resolved, "default");
107
+ });
108
+
109
+ test("#1434 an existing session coding context scopes the write (recall-then-store flow)", async () => {
110
+ const orch = makeOrchestratorStub();
111
+ orch.setCodingContextForSession("sess-ctx", {
112
+ projectId: projectTagProjectId("Blend/Supply"),
113
+ branch: null,
114
+ rootPath: projectTagProjectId("Blend/Supply"),
115
+ defaultBranch: null,
116
+ });
117
+ const service = new EngramAccessService(orch);
118
+
119
+ const resolved = await resolver(service)({
120
+ sessionKey: "sess-ctx",
121
+ authenticatedPrincipal: "alice",
122
+ content: "x",
123
+ });
124
+ assert.equal(resolved, projectNamespaceFor("Blend/Supply"));
125
+ });
126
+
127
+ test("#1434 an existing session binding wins over per-call projectTag (recall symmetry)", async () => {
128
+ // Session is bound to project A; this write also passes per-call projectTag B.
129
+ // The write MUST resolve to A — the same project the session's recall searches
130
+ // (recall is session-first: maybeAttachCodingContext returns early when a
131
+ // context is already attached). A per-call-wins write would land in B, which
132
+ // that session's recall never searches, so the memory would be undiscoverable.
133
+ const orch = makeOrchestratorStub();
134
+ orch.setCodingContextForSession("sess-reuse", {
135
+ projectId: projectTagProjectId("Project/A"),
136
+ branch: null,
137
+ rootPath: projectTagProjectId("Project/A"),
138
+ defaultBranch: null,
139
+ });
140
+ const service = new EngramAccessService(orch);
141
+ const resolved = await resolver(service)({
142
+ sessionKey: "sess-reuse",
143
+ authenticatedPrincipal: "alice",
144
+ projectTag: "Project/B",
145
+ content: "x",
146
+ });
147
+ assert.equal(resolved, projectNamespaceFor("Project/A"));
148
+ assert.notEqual(resolved, projectNamespaceFor("Project/B"));
149
+ });
150
+
151
+ test("#1434 explicit namespace wins and bypasses coding overlay", async () => {
152
+ const orch = makeOrchestratorStub();
153
+ const service = new EngramAccessService(orch);
154
+ const resolved = await resolver(service)({
155
+ sessionKey: "sess-2",
156
+ authenticatedPrincipal: "alice",
157
+ namespace: "default",
158
+ projectTag: "Blend/Supply",
159
+ content: "x",
160
+ });
161
+ assert.equal(resolved, "default");
162
+ });
163
+
164
+ test("#1434 unqualified write (self policy) stays on config.defaultNamespace", async () => {
165
+ // Even when principal "alice" has a self policy, an UNQUALIFIED write (no
166
+ // coding overlay) stays on config.defaultNamespace — exactly the pre-#1434
167
+ // behavior. #1434 only re-scopes project-identified writes; it must not
168
+ // silently move plain unqualified writes to a principal self namespace (Codex
169
+ // review). The symmetry fix applies to the coding-overlay path only.
170
+ const orch = makeOrchestratorStub({
171
+ namespacePolicies: [
172
+ { name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
173
+ ],
174
+ } as Partial<PluginConfig>);
175
+ const service = new EngramAccessService(orch);
176
+ const resolved = await resolver(service)({
177
+ sessionKey: "sess-3",
178
+ authenticatedPrincipal: "alice",
179
+ content: "x",
180
+ });
181
+ assert.equal(resolved, "default");
182
+ });
183
+
184
+ test("#1434 unqualified write with no principal policy stays on the default namespace", async () => {
185
+ // No policy named after the principal => base is defaultNamespace, so behavior
186
+ // is unchanged for the common deployment.
187
+ const orch = makeOrchestratorStub();
188
+ const service = new EngramAccessService(orch);
189
+ const resolved = await resolver(service)({
190
+ sessionKey: "sess-3b",
191
+ authenticatedPrincipal: "alice",
192
+ content: "x",
193
+ });
194
+ assert.equal(resolved, "default");
195
+ });
196
+
197
+ test("#1434 project write overlays onto the principal self base (recall symmetry)", async () => {
198
+ // With a self policy, a project-scoped write overlays onto the principal self
199
+ // base (defaultNamespaceForPrincipal) — the SAME base recall/observe/buffer
200
+ // overlay onto — so the store is discoverable by that principal's
201
+ // project-scoped recall (Cursor review / rule 42).
202
+ const orch = makeOrchestratorStub({
203
+ namespacePolicies: [
204
+ { name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
205
+ ],
206
+ } as Partial<PluginConfig>);
207
+ const service = new EngramAccessService(orch);
208
+ const resolved = await resolver(service)({
209
+ sessionKey: "sess-3c",
210
+ authenticatedPrincipal: "alice",
211
+ projectTag: "Blend/Supply",
212
+ content: "x",
213
+ });
214
+ assert.equal(
215
+ resolved,
216
+ combineNamespaces("alice", projectNamespaceName(projectTagProjectId("Blend/Supply"))),
217
+ );
218
+ });
219
+
220
+ test("#1434 an explicit coding-overlay namespace string is NOT a writable target", async () => {
221
+ // Project scoping is requested via cwd/projectTag, never by naming the derived
222
+ // overlay namespace. A caller naming an overlay-shaped namespace directly is
223
+ // authorized strictly through canWriteNamespace and rejected, so the persist
224
+ // allow-list can never be bypassed by guessing an overlay name.
225
+ const orch = makeOrchestratorStub();
226
+ const service = new EngramAccessService(orch);
227
+ await assert.rejects(
228
+ resolver(service)({
229
+ sessionKey: "sess-explicit-overlay",
230
+ authenticatedPrincipal: "alice",
231
+ namespace: projectNamespaceFor("Blend/Supply"), // "default-project-…"
232
+ content: "x",
233
+ }),
234
+ /not writable/,
235
+ );
236
+ });
237
+
238
+ test("#1434 a prefix-colliding principal namespace cannot be written cross-tenant (Codex P1)", async () => {
239
+ // Policies for both `alice` and `alice-project-team`. An authenticated `alice`
240
+ // must NOT be able to write `alice-project-team-project-foo` (the OTHER
241
+ // principal's project-scoped namespace) by exploiting a shared `alice-project-`
242
+ // prefix. Strict canWriteNamespace authorization rejects it.
243
+ const orch = makeOrchestratorStub({
244
+ namespacePolicies: [
245
+ { name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
246
+ {
247
+ name: "alice-project-team",
248
+ readPrincipals: ["teamuser"],
249
+ writePrincipals: ["teamuser"],
250
+ },
251
+ ],
252
+ } as Partial<PluginConfig>);
253
+ const service = new EngramAccessService(orch);
254
+ await assert.rejects(
255
+ resolver(service)({
256
+ sessionKey: "sess-collide",
257
+ authenticatedPrincipal: "alice",
258
+ namespace: "alice-project-team-project-foo",
259
+ content: "x",
260
+ }),
261
+ /not writable/,
262
+ );
263
+ });
264
+
265
+ test("#1434 a derived overlay base the principal cannot write is rejected (Codex P1)", async () => {
266
+ // The principal has a self policy but NO write access to the configured
267
+ // default namespace. An explicit `default-project-foo` must be rejected —
268
+ // overlay namespaces are never accepted as caller strings, and the base must
269
+ // pass canWriteNamespace.
270
+ const orch = makeOrchestratorStub({
271
+ defaultNamespace: "default",
272
+ namespacePolicies: [
273
+ { name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
274
+ { name: "default", readPrincipals: ["admin"], writePrincipals: ["admin"] },
275
+ ],
276
+ } as Partial<PluginConfig>);
277
+ const service = new EngramAccessService(orch);
278
+ await assert.rejects(
279
+ resolver(service)({
280
+ sessionKey: "sess-base-noauth",
281
+ authenticatedPrincipal: "alice",
282
+ namespace: "default-project-foo",
283
+ content: "x",
284
+ }),
285
+ /not writable/,
286
+ );
287
+ });
288
+
289
+ test("#1434 a forged cross-principal namespace cannot widen access", async () => {
290
+ // A caller naming a namespace that is not writable for this principal is
291
+ // rejected by canWriteNamespace — it can't escalate to another principal's
292
+ // namespace.
293
+ const orch = makeOrchestratorStub();
294
+ const service = new EngramAccessService(orch);
295
+ await assert.rejects(
296
+ resolver(service)({
297
+ sessionKey: "sess-forge",
298
+ authenticatedPrincipal: "alice",
299
+ namespace: "victim-secret",
300
+ content: "x",
301
+ }),
302
+ /not writable/,
303
+ );
304
+ });
305
+
306
+ test("#1434 namespaces disabled: cwd/projectTag are a no-op (common single-tenant MCP case)", async () => {
307
+ const orch = makeOrchestratorStub({ namespacesEnabled: false } as Partial<PluginConfig>);
308
+ const service = new EngramAccessService(orch);
309
+ const resolved = await resolver(service)({
310
+ sessionKey: "sess-4",
311
+ projectTag: "Blend/Supply",
312
+ content: "x",
313
+ });
314
+ assert.equal(resolved, "default");
315
+ });
316
+
317
+ function makeAttachOrchestrator() {
318
+ const contexts = new Map<string, CodingContext>();
319
+ const getStorageCalls: Array<string | undefined> = [];
320
+ const orch = {
321
+ config: {
322
+ namespacesEnabled: true,
323
+ defaultNamespace: "default",
324
+ sharedNamespace: "shared",
325
+ namespacePolicies: [],
326
+ codingMode: { projectScope: true },
327
+ memoryDir: "/synthetic/remnic-coding-write-attach",
328
+ recallCrossNamespaceBudgetEnabled: false,
329
+ recallCrossNamespaceBudgetWindowMs: 60_000,
330
+ recallCrossNamespaceBudgetSoftLimit: 10,
331
+ recallCrossNamespaceBudgetHardLimit: 30,
332
+ },
333
+ getCodingContextForSession: (sk: string) => contexts.get(sk) ?? null,
334
+ setCodingContextForSession: (sk: string, ctx: CodingContext) => {
335
+ contexts.set(sk, ctx);
336
+ },
337
+ getStorage: async (ns?: string) => {
338
+ getStorageCalls.push(ns);
339
+ return {
340
+ readAllMemories: async () => [],
341
+ writeMemory: async () => "mem-1",
342
+ appendMemoryLifecycleEvents: async () => {},
343
+ };
344
+ },
345
+ } as unknown as Orchestrator;
346
+ return { orch, contexts, getStorageCalls };
347
+ }
348
+
349
+ function storeRequest(
350
+ overrides: Record<string, unknown>,
351
+ ): Parameters<EngramAccessService["memoryStore"]>[0] {
352
+ return {
353
+ authenticatedPrincipal: "alice",
354
+ content: "durable project memory",
355
+ category: "fact",
356
+ confidence: 0.9,
357
+ tags: [],
358
+ ...overrides,
359
+ } as unknown as Parameters<EngramAccessService["memoryStore"]>[0];
360
+ }
361
+
362
+ test("#1434 a real memory_store attaches coding context so a later bare recall on the session is scoped (Cursor review)", async () => {
363
+ // A store with sessionKey + per-call projectTag must seed the session's
364
+ // coding binding (like recall's maybeAttachCodingContext), so a SUBSEQUENT
365
+ // bare recall on the same session — one that omits cwd/projectTag — is scoped
366
+ // to the same project and finds the memory.
367
+ const { orch, contexts, getStorageCalls } = makeAttachOrchestrator();
368
+ const service = new EngramAccessService(orch);
369
+
370
+ const res = await service.memoryStore(
371
+ storeRequest({ sessionKey: "sess-attach", projectTag: "Blend/Supply" }),
372
+ );
373
+
374
+ assert.equal(res.status, "stored");
375
+ assert.equal(res.namespace, projectNamespaceFor("Blend/Supply"));
376
+ // The store attached the coding context the recall path reads.
377
+ assert.equal(
378
+ contexts.get("sess-attach")?.projectId,
379
+ projectTagProjectId("Blend/Supply"),
380
+ );
381
+ assert.ok(
382
+ getStorageCalls.every((ns) => ns === projectNamespaceFor("Blend/Supply")),
383
+ `expected all getStorage calls on the project namespace, got ${JSON.stringify(getStorageCalls)}`,
384
+ );
385
+ // A later BARE resolve (no per-call context) on the same session — what a
386
+ // subsequent recall on this session uses — is now scoped to the same project.
387
+ const bare = await resolver(service)({
388
+ sessionKey: "sess-attach",
389
+ authenticatedPrincipal: "alice",
390
+ content: "y",
391
+ });
392
+ assert.equal(bare, projectNamespaceFor("Blend/Supply"));
393
+ });
394
+
395
+ test("#1434 an explicit-namespace store does NOT bind the session to a project (Codex review)", async () => {
396
+ // An explicit `namespace` bypasses the coding overlay, so the write must not
397
+ // seed a project binding the session never wrote to — else later bare recalls
398
+ // would search a project namespace with no committed memory.
399
+ const { orch, contexts } = makeAttachOrchestrator();
400
+ const service = new EngramAccessService(orch);
401
+ const res = await service.memoryStore(
402
+ storeRequest({ sessionKey: "sess-explicit", namespace: "default", projectTag: "Blend/Supply" }),
403
+ );
404
+ assert.equal(res.status, "stored");
405
+ assert.equal(res.namespace, "default");
406
+ assert.equal(contexts.get("sess-explicit"), undefined, "explicit-namespace write must not bind the session");
407
+ });
408
+
409
+ test("#1434 a dryRun store does NOT bind the session to a project (Codex review)", async () => {
410
+ // A dryRun is a read-only preview; it must not mutate session coding context.
411
+ const { orch, contexts } = makeAttachOrchestrator();
412
+ const service = new EngramAccessService(orch);
413
+ const res = await service.memoryStore(
414
+ storeRequest({ sessionKey: "sess-dry", projectTag: "Blend/Supply", dryRun: true }),
415
+ );
416
+ assert.equal(res.status, "validated");
417
+ assert.equal(contexts.get("sess-dry"), undefined, "dryRun must not bind the session");
418
+ });
419
+
420
+ // ── Persist layer (#1434 P1/High): a pre-resolved project namespace must reach
421
+ // storage instead of being rejected by the static policy allow-list. ──────────
422
+
423
+ function makePersistOrchestrator() {
424
+ const getStorageCalls: Array<string | undefined> = [];
425
+ const orch = {
426
+ config: {
427
+ namespacesEnabled: true,
428
+ defaultNamespace: "default",
429
+ sharedNamespace: "shared",
430
+ namespacePolicies: [],
431
+ },
432
+ getStorage: async (ns?: string) => {
433
+ getStorageCalls.push(ns);
434
+ return {
435
+ readAllMemories: async () => [],
436
+ writeMemory: async () => "mem-1",
437
+ appendMemoryLifecycleEvents: async () => {},
438
+ };
439
+ },
440
+ } as unknown as Orchestrator;
441
+ return { orch, getStorageCalls };
442
+ }
443
+
444
+ function candidate(overrides: Partial<ValidExplicitCapture> = {}): ValidExplicitCapture {
445
+ return {
446
+ content: "durable project memory",
447
+ category: "fact",
448
+ confidence: 0.9,
449
+ tags: [],
450
+ namespace: "default-project-tag-abc123",
451
+ ...overrides,
452
+ };
453
+ }
454
+
455
+ test("#1434 persistExplicitCapture routes a pre-resolved project namespace to storage", async () => {
456
+ const { orch, getStorageCalls } = makePersistOrchestrator();
457
+ const res = await persistExplicitCapture(
458
+ orch,
459
+ candidate({ namespacePreResolved: true }),
460
+ "memory_store",
461
+ );
462
+ assert.equal(res.id, "mem-1");
463
+ // The dynamic project namespace must be used verbatim (dup-check + write),
464
+ // never rewritten or rejected.
465
+ assert.ok(
466
+ getStorageCalls.every((ns) => ns === "default-project-tag-abc123"),
467
+ `expected all getStorage calls on the project namespace, got ${JSON.stringify(getStorageCalls)}`,
468
+ );
469
+ });
470
+
471
+ test("#1434 persistExplicitCapture still rejects an unauthorized namespace when not pre-resolved", async () => {
472
+ const { orch } = makePersistOrchestrator();
473
+ await assert.rejects(
474
+ persistExplicitCapture(orch, candidate(), "memory_store"),
475
+ /unsupported namespace/,
476
+ "the policy allow-list guard must still apply to callers that do not pre-authorize",
477
+ );
478
+ });