@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
@@ -7,7 +7,11 @@ import { AccessIdempotencyStore, hashAccessIdempotencyPayload } from "./access-i
7
7
  import { AccessAuditAdapter, type AccessAuditConfig, type AccessAuditResult } from "./access-audit.js";
8
8
  import type { AnomalyDetectorResult } from "./recall-audit-anomaly.js";
9
9
  import { resolveGitContext } from "./coding/git-context.js";
10
- import { projectTagProjectId } from "./coding/coding-namespace.js";
10
+ import {
11
+ combineNamespaces,
12
+ projectTagProjectId,
13
+ resolveCodingNamespaceOverlay,
14
+ } from "./coding/coding-namespace.js";
11
15
  import { WorkStorage } from "./work/storage.js";
12
16
  import {
13
17
  exportWorkBoardMarkdown,
@@ -91,6 +95,7 @@ import type {
91
95
  EntityFile,
92
96
  MemoryFile,
93
97
  MemoryActionOutcome,
98
+ CodingContext,
94
99
  MemoryActionType,
95
100
  MemoryLifecycleEvent,
96
101
  MemoryStatus,
@@ -755,9 +760,25 @@ export interface EngramAccessWriteEnvelope {
755
760
  authenticatedPrincipal?: string;
756
761
  }
757
762
 
758
- export interface EngramAccessMemoryStoreRequest extends EngramAccessWriteEnvelope, ExplicitCaptureInput {}
763
+ /**
764
+ * Optional git/project context for project-scoped writes (#1434). When no
765
+ * explicit `namespace` is supplied, these route the write to the same project
766
+ * namespace recall/observe resolve from `cwd`/`projectTag` (rule 42 symmetry).
767
+ */
768
+ export interface CodingScopedWriteInput {
769
+ cwd?: string;
770
+ projectTag?: string;
771
+ }
759
772
 
760
- export interface EngramAccessSuggestionSubmitRequest extends EngramAccessWriteEnvelope, ExplicitCaptureInput {}
773
+ export interface EngramAccessMemoryStoreRequest
774
+ extends EngramAccessWriteEnvelope,
775
+ ExplicitCaptureInput,
776
+ CodingScopedWriteInput {}
777
+
778
+ export interface EngramAccessSuggestionSubmitRequest
779
+ extends EngramAccessWriteEnvelope,
780
+ ExplicitCaptureInput,
781
+ CodingScopedWriteInput {}
761
782
 
762
783
  export interface EngramAccessWriteResponse {
763
784
  schemaVersion: 1;
@@ -1115,6 +1136,144 @@ export class EngramAccessService {
1115
1136
  return resolved;
1116
1137
  }
1117
1138
 
1139
+ /**
1140
+ * Resolve a coding context from `cwd`/`projectTag` WITHOUT persisting it to
1141
+ * any session — the read-only half of `maybeAttachCodingContext`. Returns
1142
+ * null when project scoping is off or nothing resolves. `projectTag` takes
1143
+ * priority over `cwd` (matching `maybeAttachCodingContext`).
1144
+ */
1145
+ private async resolveCodingContextFromOptions(
1146
+ options: CodingScopedWriteInput,
1147
+ ): Promise<CodingContext | null> {
1148
+ if (!this.orchestrator.config.codingMode?.projectScope) return null;
1149
+ if (typeof options.projectTag === "string" && options.projectTag.trim().length > 0) {
1150
+ const projectId = projectTagProjectId(options.projectTag);
1151
+ return { projectId, branch: null, rootPath: projectId, defaultBranch: null };
1152
+ }
1153
+ if (typeof options.cwd === "string" && options.cwd.trim().length > 0) {
1154
+ try {
1155
+ const gitCtx = await resolveGitContext(options.cwd);
1156
+ if (gitCtx) {
1157
+ return {
1158
+ projectId: gitCtx.projectId,
1159
+ branch: gitCtx.branch,
1160
+ rootPath: gitCtx.rootPath,
1161
+ defaultBranch: gitCtx.defaultBranch,
1162
+ };
1163
+ }
1164
+ } catch {
1165
+ // resolveGitContext never throws, but stay defensive — not being in a
1166
+ // repo is normal and must not break the write.
1167
+ }
1168
+ }
1169
+ return null;
1170
+ }
1171
+
1172
+ /**
1173
+ * Resolve the write namespace for explicit-write tools (memory_store /
1174
+ * suggestion_submit), project-scoping the write the same way recall does so a
1175
+ * memory stored with a client-injected `cwd`/`projectTag` is discoverable by
1176
+ * project-scoped recall (#1434, rule 42).
1177
+ *
1178
+ * Precedence:
1179
+ * - An explicit `namespace` always wins and is authorized strictly via
1180
+ * `resolveWritableNamespace` → `canWriteNamespace`. A coding-overlay
1181
+ * namespace string (`<base>-project-*`) is NOT a writable target via the
1182
+ * explicit field — project scoping is requested with `cwd`/`projectTag`,
1183
+ * never by naming the derived namespace — so there is no way to bypass the
1184
+ * policy allow-list by guessing/forging an overlay name (Codex review).
1185
+ * - With NO coding overlay, the write stays on `config.defaultNamespace` —
1186
+ * exactly the pre-#1434 behavior, so an unqualified write is NOT silently
1187
+ * moved to a principal self namespace (Codex review).
1188
+ * - WITH a coding overlay, the base is the principal self namespace
1189
+ * (`defaultNamespaceForPrincipal`, write-checked) — the SAME base recall,
1190
+ * observe, and the orchestrator buffer-flush write path overlay onto
1191
+ * (rule 42 / Cursor) — so a project-scoped store lands exactly where
1192
+ * project-scoped recall searches. The overlay namespace is always REBUILT
1193
+ * from the authenticated principal's base, never accepted as a caller
1194
+ * string, so a caller can never reach another principal's subtree.
1195
+ *
1196
+ * Read-only: this NEVER mutates session coding context, so the idempotency
1197
+ * peeks and dryRun preflights that call it stay side-effect free (Codex
1198
+ * review). It prefers the per-call `cwd`/`projectTag` (the project explicitly
1199
+ * identified for this write), else the session's existing context. The HTTP
1200
+ * surface lets the peek and the write each resolve independently; the peek's
1201
+ * namespace only gates rate-limiting (memory_store/suggestion_submit run their
1202
+ * own idempotency check), so a benign session-context change between the two
1203
+ * never fails a write — there is no namespace to "pin".
1204
+ */
1205
+ private async resolveCodingScopedWriteNamespace(
1206
+ request: CodingScopedWriteInput & {
1207
+ namespace?: string;
1208
+ sessionKey?: string;
1209
+ authenticatedPrincipal?: string;
1210
+ },
1211
+ ): Promise<string> {
1212
+ const hasExplicitNamespace =
1213
+ typeof request.namespace === "string" && request.namespace.trim().length > 0;
1214
+ if (hasExplicitNamespace) {
1215
+ return this.resolveWritableNamespace(
1216
+ request.namespace,
1217
+ request.sessionKey,
1218
+ request.authenticatedPrincipal,
1219
+ );
1220
+ }
1221
+ // Project scoping only applies when namespaces are enabled (else overlaying
1222
+ // would create false isolation over a single storage dir) and projectScope
1223
+ // is on. The coding context MUST be resolved exactly as the recall path
1224
+ // resolves it, or a scoped store won't be discoverable by scoped recall
1225
+ // (the whole point of #1434). Recall calls `maybeAttachCodingContext`, which
1226
+ // returns early when the session already has a context — so recall is
1227
+ // SESSION-FIRST: an existing session binding wins, and the per-call
1228
+ // cwd/projectTag is only used to seed a context when none is attached yet.
1229
+ // Mirror that precedence here: session context first, per-call as fallback
1230
+ // (Codex review — a per-call-wins write would land in a project that the
1231
+ // same session's recall, still on the bound project, never searches).
1232
+ //
1233
+ // A sessionKey is REQUIRED to apply the overlay. The recall path can only
1234
+ // attach/look up coding context per session (`maybeAttachCodingContext` and
1235
+ // `applyCodingNamespaceOverlay` both no-op without a sessionKey), so a
1236
+ // sessionless recall always searches the base namespace. A sessionless
1237
+ // write must therefore also stay on the base — otherwise a client that
1238
+ // injects cwd/projectTag but no sessionKey would store into
1239
+ // `default-project-*` that its own recall never searches (Codex review).
1240
+ const hasSession =
1241
+ typeof request.sessionKey === "string" && request.sessionKey.length > 0;
1242
+ const overlay =
1243
+ hasSession &&
1244
+ this.orchestrator.config.namespacesEnabled &&
1245
+ this.orchestrator.config.codingMode?.projectScope
1246
+ ? resolveCodingNamespaceOverlay(
1247
+ this.orchestrator.getCodingContextForSession(request.sessionKey) ??
1248
+ (await this.resolveCodingContextFromOptions(request)),
1249
+ this.orchestrator.config.codingMode,
1250
+ this.orchestrator.config.defaultNamespace,
1251
+ )
1252
+ : null;
1253
+ if (!overlay) {
1254
+ // No coding overlay → unqualified write stays on config.defaultNamespace,
1255
+ // exactly the pre-#1434 behavior (auth-checked, like the legacy path).
1256
+ return this.resolveWritableNamespace(
1257
+ undefined,
1258
+ request.sessionKey,
1259
+ request.authenticatedPrincipal,
1260
+ );
1261
+ }
1262
+ // Coding overlay → overlay onto the principal self base, the SAME namespace
1263
+ // recall/observe/buffer-flush use. The result is a principal-owned
1264
+ // `project-*` sub-namespace derived from this authorized base, so it needs
1265
+ // no separate write policy.
1266
+ const principal = this.resolveRequestPrincipal(
1267
+ request.sessionKey,
1268
+ request.authenticatedPrincipal,
1269
+ );
1270
+ const base = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
1271
+ if (!canWriteNamespace(principal, base, this.orchestrator.config)) {
1272
+ throw new EngramAccessInputError(`namespace is not writable: ${base}`);
1273
+ }
1274
+ return combineNamespaces(base, overlay.namespace);
1275
+ }
1276
+
1118
1277
  private async objectiveStateStoreLocationForNamespace(namespace: string): Promise<{
1119
1278
  memoryDir: string;
1120
1279
  objectiveStateStoreDir?: string;
@@ -1384,6 +1543,16 @@ export class EngramAccessService {
1384
1543
  idempotencyKey?: string;
1385
1544
  requestFingerprint: unknown;
1386
1545
  skip?: boolean;
1546
+ /**
1547
+ * Invoked exactly once, immediately before an ACTUAL (non-replay, non-skip)
1548
+ * write is committed — atomically with the idempotency miss determination.
1549
+ * The HTTP surface uses this to enforce the write rate limit against the
1550
+ * real write/miss (and the real resolved namespace), so a namespace-divergent
1551
+ * idempotency peek can never let a fresh write skip the quota check (#1434
1552
+ * Codex review). It is NOT called on dryRun (skip) or replay, preserving the
1553
+ * replay-bypasses-a-full-window behavior.
1554
+ */
1555
+ beforeExecute?: () => void | Promise<void>;
1387
1556
  execute: () => Promise<T>;
1388
1557
  }): Promise<T> {
1389
1558
  if (options.skip === true) {
@@ -1391,6 +1560,7 @@ export class EngramAccessService {
1391
1560
  }
1392
1561
  const key = options.idempotencyKey?.trim();
1393
1562
  if (!key) {
1563
+ if (options.beforeExecute) await options.beforeExecute();
1394
1564
  return options.execute();
1395
1565
  }
1396
1566
  return this.withIdempotencyLock(key, async () => {
@@ -1409,6 +1579,7 @@ export class EngramAccessService {
1409
1579
  idempotencyReplay: true,
1410
1580
  };
1411
1581
  }
1582
+ if (options.beforeExecute) await options.beforeExecute();
1412
1583
  const response = await options.execute();
1413
1584
  await this.idempotency.put(key, requestHash, response);
1414
1585
  return response;
@@ -1757,6 +1928,28 @@ export class EngramAccessService {
1757
1928
  }
1758
1929
  }
1759
1930
 
1931
+ /**
1932
+ * Seed the session's coding binding AFTER a committed, project-scoped explicit
1933
+ * write (memory_store / suggestion_submit), mirroring the recall path's
1934
+ * `maybeAttachCodingContext` so a later bare recall/write on the same session
1935
+ * is scoped to the same project. Called only from the post-persist path, so it
1936
+ * never fires on dryRun, replay/conflict, or quota-rejected requests. Skips
1937
+ * when an explicit `namespace` was supplied — that write bypassed the coding
1938
+ * overlay, so binding the session to a project it never wrote to would make
1939
+ * later bare recalls miss (Codex review).
1940
+ */
1941
+ private async attachCodingContextAfterScopedWrite(
1942
+ request: CodingScopedWriteInput & { namespace?: string; sessionKey?: string },
1943
+ ): Promise<void> {
1944
+ const hasExplicitNamespace =
1945
+ typeof request.namespace === "string" && request.namespace.trim().length > 0;
1946
+ if (hasExplicitNamespace) return;
1947
+ await this.maybeAttachCodingContext(request.sessionKey, {
1948
+ cwd: request.cwd,
1949
+ projectTag: request.projectTag,
1950
+ });
1951
+ }
1952
+
1760
1953
  async recall(request: EngramAccessRecallRequest): Promise<EngramAccessRecallResponse> {
1761
1954
  const query = request.query.trim();
1762
1955
  if (query.length === 0) {
@@ -2662,12 +2855,11 @@ export class EngramAccessService {
2662
2855
  // per-tenant) do not block each other.
2663
2856
  private xrayQueue: Promise<void> = Promise.resolve();
2664
2857
 
2665
- async memoryStore(request: EngramAccessMemoryStoreRequest): Promise<EngramAccessWriteResponse> {
2666
- const namespace = this.resolveWritableNamespace(
2667
- request.namespace,
2668
- request.sessionKey,
2669
- request.authenticatedPrincipal,
2670
- );
2858
+ async memoryStore(
2859
+ request: EngramAccessMemoryStoreRequest,
2860
+ hooks?: { enforceWriteQuota?: () => void | Promise<void> },
2861
+ ): Promise<EngramAccessWriteResponse> {
2862
+ const namespace = await this.resolveCodingScopedWriteNamespace(request);
2671
2863
  const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
2672
2864
  if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
2673
2865
  throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
@@ -2687,6 +2879,14 @@ export class EngramAccessService {
2687
2879
  };
2688
2880
  }
2689
2881
  const result = await persistExplicitCapture(this.orchestrator, candidate, "memory_store");
2882
+ // Seed the session's coding binding ONLY after a real write commits, and
2883
+ // only when the namespace came from project scoping (no explicit
2884
+ // namespace). This mirrors recall's maybeAttachCodingContext so a LATER
2885
+ // bare recall/write on the same session is scoped to the same project —
2886
+ // but never binds the session on a dryRun, replay/conflict, quota
2887
+ // rejection, or an explicit-namespace write (which bypasses the overlay),
2888
+ // since those don't reach this point or aren't project-scoped (Codex review).
2889
+ await this.attachCodingContextAfterScopedWrite(request);
2690
2890
  const response: EngramAccessWriteResponse = {
2691
2891
  schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
2692
2892
  operation: "memory_store",
@@ -2719,16 +2919,13 @@ export class EngramAccessService {
2719
2919
  sourceReason: request.sourceReason,
2720
2920
  },
2721
2921
  skip: request.dryRun === true,
2922
+ beforeExecute: hooks?.enforceWriteQuota,
2722
2923
  execute,
2723
2924
  });
2724
2925
  }
2725
2926
 
2726
2927
  async peekMemoryStoreIdempotency(request: EngramAccessMemoryStoreRequest): Promise<EngramAccessIdempotencyStatus> {
2727
- const namespace = this.resolveWritableNamespace(
2728
- request.namespace,
2729
- request.sessionKey,
2730
- request.authenticatedPrincipal,
2731
- );
2928
+ const namespace = await this.resolveCodingScopedWriteNamespace(request);
2732
2929
  const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
2733
2930
  if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
2734
2931
  throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
@@ -2751,12 +2948,11 @@ export class EngramAccessService {
2751
2948
  });
2752
2949
  }
2753
2950
 
2754
- async suggestionSubmit(request: EngramAccessSuggestionSubmitRequest): Promise<EngramAccessWriteResponse> {
2755
- const namespace = this.resolveWritableNamespace(
2756
- request.namespace,
2757
- request.sessionKey,
2758
- request.authenticatedPrincipal,
2759
- );
2951
+ async suggestionSubmit(
2952
+ request: EngramAccessSuggestionSubmitRequest,
2953
+ hooks?: { enforceWriteQuota?: () => void | Promise<void> },
2954
+ ): Promise<EngramAccessWriteResponse> {
2955
+ const namespace = await this.resolveCodingScopedWriteNamespace(request);
2760
2956
  const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
2761
2957
  if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
2762
2958
  throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
@@ -2781,6 +2977,10 @@ export class EngramAccessService {
2781
2977
  "suggestion_submit",
2782
2978
  new Error(request.sourceReason?.trim() || "submitted via engram suggestion_submit"),
2783
2979
  );
2980
+ // Seed the session binding only after a real, project-scoped submit commits
2981
+ // (mirrors memory_store / recall; skips dryRun, replay, quota-reject, and
2982
+ // explicit-namespace writes — Codex review).
2983
+ await this.attachCodingContextAfterScopedWrite(request);
2784
2984
  const response: EngramAccessWriteResponse = {
2785
2985
  schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
2786
2986
  operation: "suggestion_submit",
@@ -2813,6 +3013,7 @@ export class EngramAccessService {
2813
3013
  sourceReason: request.sourceReason,
2814
3014
  },
2815
3015
  skip: request.dryRun === true,
3016
+ beforeExecute: hooks?.enforceWriteQuota,
2816
3017
  execute,
2817
3018
  });
2818
3019
  }
@@ -2820,11 +3021,7 @@ export class EngramAccessService {
2820
3021
  async peekSuggestionSubmitIdempotency(
2821
3022
  request: EngramAccessSuggestionSubmitRequest,
2822
3023
  ): Promise<EngramAccessIdempotencyStatus> {
2823
- const namespace = this.resolveWritableNamespace(
2824
- request.namespace,
2825
- request.sessionKey,
2826
- request.authenticatedPrincipal,
2827
- );
3024
+ const namespace = await this.resolveCodingScopedWriteNamespace(request);
2828
3025
  const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
2829
3026
  if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
2830
3027
  throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
@@ -2852,13 +3049,21 @@ export class EngramAccessService {
2852
3049
  namespace: string,
2853
3050
  ): ValidExplicitCapture {
2854
3051
  try {
2855
- return validateExplicitCaptureInput(
2856
- {
2857
- ...request,
2858
- namespace,
2859
- },
2860
- "legacy_tool",
2861
- );
3052
+ return {
3053
+ ...validateExplicitCaptureInput(
3054
+ {
3055
+ ...request,
3056
+ namespace,
3057
+ },
3058
+ "legacy_tool",
3059
+ ),
3060
+ // The namespace was resolved AND authorized by
3061
+ // resolveCodingScopedWriteNamespace (explicit namespaces via
3062
+ // resolveWritableNamespace; otherwise an auth-checked base + a
3063
+ // session-owned project overlay), so the persist/queue layer must not
3064
+ // re-reject a legitimately-derived dynamic project namespace (#1434).
3065
+ namespacePreResolved: true,
3066
+ };
2862
3067
  } catch (error) {
2863
3068
  const message = error instanceof Error ? error.message : String(error);
2864
3069
  throw new EngramAccessInputError(message);
@@ -198,6 +198,46 @@ test("active recall cache hits report cache-hit latency instead of reusing gener
198
198
  assert.equal(second.latencyMs, 1);
199
199
  });
200
200
 
201
+ test("active recall cache hits append a fresh transcript entry", async () => {
202
+ const transcriptDir = await mkdtemp(path.join(os.tmpdir(), "active-recall-cache-transcript-"));
203
+ let generateCalls = 0;
204
+ const engine = createActiveRecallEngine(
205
+ {
206
+ async recall() {
207
+ return "CI worker drain after Redis reconnect storm.";
208
+ },
209
+ async generateSummary() {
210
+ generateCalls += 1;
211
+ return { text: "Redis reconnect storm caused the worker drain." };
212
+ },
213
+ now: (() => {
214
+ let tick = 40_000;
215
+ return () => tick++;
216
+ })(),
217
+ },
218
+ baseConfig({
219
+ cacheTtlMs: 5000,
220
+ persistTranscripts: true,
221
+ transcriptDir,
222
+ }),
223
+ );
224
+
225
+ const first = await engine.run(baseInput());
226
+ const second = await engine.run(baseInput());
227
+
228
+ assert.equal(generateCalls, 1);
229
+ assert.equal(first.cacheHit, false);
230
+ assert.equal(second.cacheHit, true);
231
+ assert.ok(first.transcriptPath, "expected first transcript path");
232
+ assert.ok(second.transcriptPath, "expected cache-hit transcript path");
233
+
234
+ const raw = await readFile(second.transcriptPath ?? "", "utf8");
235
+ const entries = raw.trim().split("\n").map((line) => JSON.parse(line) as { cacheHit: boolean });
236
+ assert.equal(entries.length, 2);
237
+ assert.equal(entries[0].cacheHit, false);
238
+ assert.equal(entries[1].cacheHit, true);
239
+ });
240
+
201
241
  test("active recall cache stores an isolated snapshot instead of a mutable caller reference", async () => {
202
242
  let generateCalls = 0;
203
243
  const engine = createActiveRecallEngine(
@@ -361,11 +361,26 @@ export function createActiveRecallEngine(
361
361
  }
362
362
  const cached = cache.get(cacheKey);
363
363
  if (cacheEnabled && cached) {
364
- return {
364
+ const result: ActiveRecallResult = {
365
365
  ...cloneRecallResult(cached.value),
366
366
  latencyMs: Math.max(0, now() - currentTime),
367
367
  cacheHit: true,
368
368
  };
369
+ result.transcriptPath = null;
370
+ if (config.persistTranscripts) {
371
+ try {
372
+ result.transcriptPath = await appendActiveRecallTranscript(
373
+ config.transcriptDir,
374
+ input,
375
+ config,
376
+ result,
377
+ queryBundle,
378
+ );
379
+ } catch {
380
+ result.transcriptPath = null;
381
+ }
382
+ }
383
+ return result;
369
384
  }
370
385
 
371
386
  const start = currentTime;
@@ -451,9 +466,11 @@ export function createActiveRecallEngine(
451
466
 
452
467
  if (cacheEnabled) {
453
468
  const completedAt = now();
469
+ const cachedValue = cloneRecallResult(result);
470
+ cachedValue.transcriptPath = null;
454
471
  cache.set(cacheKey, {
455
472
  expiresAt: completedAt + config.cacheTtlMs,
456
- value: cloneRecallResult(result),
473
+ value: cachedValue,
457
474
  });
458
475
  enforceCacheLimit(cache);
459
476
  }
@@ -68,13 +68,15 @@ function aggregateSignalPressure(signals: BehaviorSignalEvent[]): number {
68
68
  }
69
69
 
70
70
  function selectRecentSignals(input: BehaviorLearnerInput): BehaviorSignalEvent[] {
71
- if (input.learningWindowDays <= 0) return [...input.signals];
72
71
  const nowMs = (input.now ?? new Date()).getTime();
73
- const minTs = nowMs - input.learningWindowDays * 86_400_000;
72
+ const minTs =
73
+ input.learningWindowDays <= 0
74
+ ? Number.NEGATIVE_INFINITY
75
+ : nowMs - input.learningWindowDays * 86_400_000;
74
76
  return input.signals.filter((signal) => {
75
77
  const ts = Date.parse(signal.timestamp);
76
78
  if (!Number.isFinite(ts)) return false;
77
- return ts >= minTs;
79
+ return ts >= minTs && ts <= nowMs;
78
80
  });
79
81
  }
80
82
 
@@ -2,6 +2,7 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { SmartBuffer } from "./buffer.js";
4
4
  import { parseConfig } from "./config.js";
5
+ import type { BufferSurpriseProbe } from "./buffer.js";
5
6
  import type { BufferState, BufferTurn } from "./types.js";
6
7
 
7
8
  class FakeStorage {
@@ -111,6 +112,63 @@ test("SmartBuffer serializes concurrent addTurn mutations", async () => {
111
112
  );
112
113
  });
113
114
 
115
+ test("SmartBuffer ignores stale surprise promotion after newer turns arrive", async () => {
116
+ const storage = new FakeStorage({
117
+ turns: [],
118
+ lastExtractionAt: null,
119
+ extractionCount: 0,
120
+ });
121
+ let resolveFirstProbe = (_score: number): void => {
122
+ throw new Error("first surprise probe did not start");
123
+ };
124
+ let markFirstProbeStarted = (): void => {
125
+ throw new Error("probe start marker was not initialized");
126
+ };
127
+ const firstProbeStarted = new Promise<void>((resolve) => {
128
+ markFirstProbeStarted = resolve;
129
+ });
130
+ const probe: BufferSurpriseProbe = {
131
+ async scoreTurn(_bufferKey, turn) {
132
+ if (turn.content !== "turn A") return null;
133
+ markFirstProbeStarted();
134
+ return new Promise<number>((probeResolve) => {
135
+ resolveFirstProbe = probeResolve;
136
+ });
137
+ },
138
+ };
139
+ const buffer = new SmartBuffer(
140
+ parseConfig({
141
+ bufferSurpriseTriggerEnabled: true,
142
+ bufferSurpriseThreshold: 0.35,
143
+ bufferSurpriseProbeTimeoutMs: 10_000,
144
+ triggerMode: "smart",
145
+ }),
146
+ storage as any,
147
+ probe,
148
+ );
149
+
150
+ const firstOutcomePromise = buffer.addTurnWithOutcome(
151
+ "thread-a",
152
+ makeTurn("thread-a", "turn A"),
153
+ );
154
+ await firstProbeStarted;
155
+
156
+ const secondOutcome = await buffer.addTurnWithOutcome(
157
+ "thread-a",
158
+ makeTurn("thread-a", "turn B"),
159
+ );
160
+ assert.deepEqual(secondOutcome, { decision: "keep_buffering" });
161
+
162
+ resolveFirstProbe(1);
163
+ const firstOutcome = await firstOutcomePromise;
164
+
165
+ assert.deepEqual(firstOutcome, { decision: "keep_buffering" });
166
+ assert.deepEqual(
167
+ buffer.getTurns("thread-a").map((turn) => turn.content),
168
+ ["turn A", "turn B"],
169
+ );
170
+ });
171
+
114
172
  test("SmartBuffer clearAfterExtraction only clears the targeted logical session", async () => {
115
173
  const storage = new FakeStorage({
116
174
  turns: [],
@@ -643,7 +643,7 @@ test("flag on: slow-probe promotion survives prefix clears before the scored tur
643
643
  );
644
644
  });
645
645
 
646
- test("flag on: slow-probe promotion survives later appends to same buffer", async () => {
646
+ test("flag on: slow-probe promotion is ignored after later appends to same buffer", async () => {
647
647
  const storage = new FakeStorage(emptyBuffer());
648
648
  let releaseOldProbe: (() => void) | null = null;
649
649
  let oldProbeStarted: (() => void) | null = null;
@@ -684,8 +684,8 @@ test("flag on: slow-probe promotion survives later appends to same buffer", asyn
684
684
  (releaseOldProbe as () => void)();
685
685
  assert.equal(
686
686
  await oldDecision,
687
- "extract_now",
688
- "a later append must not suppress a valid surprise flush for the older turn",
687
+ "keep_buffering",
688
+ "a later append must suppress stale surprise promotion for the older turn",
689
689
  );
690
690
  const turns = buffer.getTurns("sess-1");
691
691
  assert.equal(turns.length, 2);
@@ -695,18 +695,9 @@ test("flag on: slow-probe promotion survives later appends to same buffer", asyn
695
695
 
696
696
  test("flag on: extraction clear preserves turns appended after the queued snapshot", async () => {
697
697
  const storage = new FakeStorage(emptyBuffer());
698
- let releaseOldProbe: (() => void) | null = null;
699
- let oldProbeStarted: (() => void) | null = null;
700
- const oldProbeStartedPromise = new Promise<void>((resolve) => {
701
- oldProbeStarted = resolve;
702
- });
703
698
  const probe: BufferSurpriseProbe = {
704
699
  async scoreTurn(_key, turn) {
705
- if (turn.content !== "old surprising turn") return null;
706
- oldProbeStarted?.();
707
- return new Promise<number | null>((resolve) => {
708
- releaseOldProbe = () => resolve(0.99);
709
- });
700
+ return turn.content === "old surprising turn" ? 0.99 : null;
710
701
  },
711
702
  };
712
703
  const config = parseConfig({
@@ -722,11 +713,6 @@ test("flag on: extraction clear preserves turns appended after the queued snapsh
722
713
  "sess-1",
723
714
  makeTurn("sess-1", "old surprising turn"),
724
715
  );
725
- await oldProbeStartedPromise;
726
-
727
- await buffer.addTurn("sess-1", makeTurn("sess-1", "newer ordinary turn"));
728
- assert.ok(releaseOldProbe);
729
- (releaseOldProbe as () => void)();
730
716
  assert.equal(await oldDecision, "extract_now");
731
717
 
732
718
  const queuedSnapshot = buffer.getTurns("sess-1");