@remnic/core 9.3.751 → 9.3.752

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 (319) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +29 -29
  3. package/dist/access-cli.js +88 -88
  4. package/dist/access-http.d.ts +5 -5
  5. package/dist/access-http.js +35 -35
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +34 -34
  8. package/dist/access-operations-batch.js +31 -31
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +33 -33
  11. package/dist/access-recall-surface.d.ts +363 -0
  12. package/dist/access-recall-surface.js +128 -0
  13. package/dist/access-schema.js +7 -7
  14. package/dist/{access-service-DmAEOJdJ.d.ts → access-service-C2cOHQvg.d.ts} +7 -26
  15. package/dist/access-service.d.ts +5 -5
  16. package/dist/access-service.js +28 -28
  17. package/dist/access-surface-catalog.d.ts +5 -5
  18. package/dist/action-confidence.d.ts +1 -1
  19. package/dist/active-memory-bridge.d.ts +1 -1
  20. package/dist/active-recall.d.ts +1 -1
  21. package/dist/active-recall.js +1 -1
  22. package/dist/behavior-learner.d.ts +1 -1
  23. package/dist/behavior-signals.d.ts +1 -1
  24. package/dist/bootstrap.d.ts +4 -4
  25. package/dist/briefing.d.ts +1 -1
  26. package/dist/briefing.js +4 -4
  27. package/dist/buffer-surprise-report.d.ts +1 -1
  28. package/dist/buffer.d.ts +1 -1
  29. package/dist/buffer.js +2 -2
  30. package/dist/calibration.d.ts +1 -1
  31. package/dist/capabilities.d.ts +1 -1
  32. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-CZJSLEFG.js} +3 -3
  33. package/dist/{catalog-BpOcwHdE.d.ts → catalog-D_FvzTM0.d.ts} +1 -1
  34. package/dist/causal-behavior.d.ts +1 -1
  35. package/dist/causal-behavior.js +2 -2
  36. package/dist/causal-chain.js +2 -2
  37. package/dist/causal-consolidation.d.ts +1 -1
  38. package/dist/causal-consolidation.js +9 -9
  39. package/dist/causal-retrieval.js +2 -2
  40. package/dist/causal-trajectory-graph.d.ts +1 -1
  41. package/dist/causal-trajectory.js +1 -1
  42. package/dist/{chunk-2NLLXCJG.js → chunk-2BFLDH5F.js} +2 -2
  43. package/dist/{chunk-UU32EAIY.js → chunk-2DPCAW2T.js} +5 -5
  44. package/dist/{chunk-5PZCDUJ6.js → chunk-2U4E34Y3.js} +4 -4
  45. package/dist/{chunk-JRZV3767.js → chunk-33NX5BLR.js} +4 -4
  46. package/dist/{chunk-X7Y7WX73.js → chunk-437S3G37.js} +2 -2
  47. package/dist/{chunk-HAZVVOYT.js → chunk-5DXUE7GB.js} +2 -2
  48. package/dist/{chunk-4DLFJJOQ.js → chunk-5JAT4ZJD.js} +4 -4
  49. package/dist/{chunk-RS25QOKZ.js → chunk-64SE4X5G.js} +2 -2
  50. package/dist/{chunk-QRRBXD24.js → chunk-67JRXV6Z.js} +7 -7
  51. package/dist/{chunk-WA7FECSG.js → chunk-6ROCH5JV.js} +7 -7
  52. package/dist/{chunk-5QANTTY2.js → chunk-BIAMJUUJ.js} +4 -4
  53. package/dist/{chunk-IKHKHADN.js → chunk-C4LNKQ3M.js} +5 -5
  54. package/dist/{chunk-B35L45HZ.js → chunk-DC5OBZS3.js} +2 -2
  55. package/dist/{chunk-ORAK3LNZ.js → chunk-ER3CCWK2.js} +2 -2
  56. package/dist/{chunk-SD34EK4Z.js → chunk-FEELYTRT.js} +5 -5
  57. package/dist/{chunk-KQAFEZQX.js → chunk-FLVL4L3L.js} +2 -2
  58. package/dist/{chunk-WIWPSQYU.js → chunk-FXRFFYWD.js} +4 -4
  59. package/dist/{chunk-W3M34XT4.js → chunk-GECEETQ3.js} +4 -4
  60. package/dist/{chunk-WWSXVOGY.js → chunk-GFGYHLON.js} +2 -2
  61. package/dist/{chunk-BJMBJZ2Y.js → chunk-GWTFLF2P.js} +59 -59
  62. package/dist/chunk-GWTFLF2P.js.map +1 -0
  63. package/dist/{chunk-WOX2UW4K.js → chunk-IBPEVGP5.js} +2 -2
  64. package/dist/{chunk-JZ7P26GF.js → chunk-IQWFWSCP.js} +4 -4
  65. package/dist/{chunk-6SXNQFHB.js → chunk-J2JB6BHT.js} +8 -8
  66. package/dist/{chunk-K2JM4DZZ.js → chunk-JHZ3LEM7.js} +2 -2
  67. package/dist/{chunk-YGGXUNS4.js → chunk-JNKSFIAY.js} +45 -45
  68. package/dist/{chunk-CGZWGVQF.js → chunk-K5CWCJKJ.js} +6 -6
  69. package/dist/{chunk-TPLE6SNY.js → chunk-KFNG72W7.js} +7 -7
  70. package/dist/{chunk-4NWIGAIC.js → chunk-KT2VQLP7.js} +8 -8
  71. package/dist/{chunk-6FNZ3NMS.js → chunk-L3VSTJ2V.js} +2 -2
  72. package/dist/{chunk-ARV3AUOM.js → chunk-M5QKGHCR.js} +2 -2
  73. package/dist/{chunk-E4PFDD3N.js → chunk-MEPWWSTD.js} +4 -4
  74. package/dist/{chunk-V5RVMULT.js → chunk-MLDAD27I.js} +7 -7
  75. package/dist/{chunk-K2R3DEEH.js → chunk-MTN2G4DT.js} +2 -2
  76. package/dist/{chunk-DBSMLRTO.js → chunk-MW7UUXTA.js} +5 -5
  77. package/dist/{chunk-OSMBC3JU.js → chunk-OKOF7ITI.js} +6 -6
  78. package/dist/{chunk-DBRVU5PR.js → chunk-OVMLS7TN.js} +3 -3
  79. package/dist/{chunk-NNL4LJAD.js → chunk-QMMCMV2L.js} +5 -5
  80. package/dist/{chunk-RNWWAS5V.js → chunk-QRM3K3XT.js} +5 -5
  81. package/dist/{chunk-HM6UJPPO.js → chunk-R7TGDLV3.js} +4 -4
  82. package/dist/{chunk-EEKDRAWX.js → chunk-RBYV3WSB.js} +4 -4
  83. package/dist/{chunk-LOPZ2DWR.js → chunk-RRRZ3CLN.js} +4 -4
  84. package/dist/{chunk-7TWA7DKP.js → chunk-S5UYWK3O.js} +6 -6
  85. package/dist/{chunk-QOGXUNWL.js → chunk-SC5AEZLU.js} +9 -9
  86. package/dist/{chunk-SNEXVX3U.js → chunk-UFNQKZV7.js} +15 -15
  87. package/dist/{chunk-W3BFT5OJ.js → chunk-V4ZPLQLC.js} +2 -2
  88. package/dist/{chunk-SQNED75R.js → chunk-V5UV7BOA.js} +1 -1
  89. package/dist/{chunk-RWZB6MAY.js → chunk-VQL22SHO.js} +891 -1002
  90. package/dist/chunk-VQL22SHO.js.map +1 -0
  91. package/dist/{chunk-UETBEHBA.js → chunk-VX2RYM2K.js} +2232 -1942
  92. package/dist/chunk-VX2RYM2K.js.map +1 -0
  93. package/dist/{chunk-K5LLP4CY.js → chunk-XKAOT4ZB.js} +4 -4
  94. package/dist/{chunk-ZBVQ4ZM4.js → chunk-YGNQG4Q6.js} +8 -8
  95. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  96. package/dist/{chunk-HYI2GP3F.js → chunk-ZGGI3COG.js} +9 -9
  97. package/dist/{cli-lmf5RGyQ.d.ts → cli-DFUU9WYu.d.ts} +3 -3
  98. package/dist/cli.d.ts +6 -6
  99. package/dist/cli.js +60 -60
  100. package/dist/compounding/engine.d.ts +1 -1
  101. package/dist/compounding/engine.js +6 -6
  102. package/dist/compounding/preference-consolidator.d.ts +1 -1
  103. package/dist/compression-optimizer.d.ts +1 -1
  104. package/dist/config.d.ts +1 -1
  105. package/dist/config.js +1 -1
  106. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  107. package/dist/connectors/codex-materialize-runner.js +5 -5
  108. package/dist/connectors/codex-materialize.d.ts +1 -1
  109. package/dist/connectors/index.d.ts +1 -1
  110. package/dist/connectors/index.js +7 -7
  111. package/dist/consolidation-provenance-check.d.ts +1 -1
  112. package/dist/consolidation-undo.d.ts +1 -1
  113. package/dist/contradiction/index.d.ts +1 -1
  114. package/dist/contradiction/index.js +2 -2
  115. package/dist/{contradiction-scan-3FJYWS3G.js → contradiction-scan-KO4WWCS4.js} +3 -3
  116. package/dist/contradiction-scan-KO4WWCS4.js.map +1 -0
  117. package/dist/conversation-index/backend.d.ts +1 -1
  118. package/dist/conversation-index/chunker.d.ts +1 -1
  119. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  120. package/dist/conversation-index/indexer.d.ts +1 -1
  121. package/dist/conversation-index/search.d.ts +1 -1
  122. package/dist/dashboard-runtime.js +2 -2
  123. package/dist/day-summary.d.ts +1 -1
  124. package/dist/day-summary.js +2 -2
  125. package/dist/delinearize.d.ts +1 -1
  126. package/dist/direct-answer-wiring.d.ts +1 -1
  127. package/dist/direct-answer.d.ts +1 -1
  128. package/dist/embedding-fallback.d.ts +1 -1
  129. package/dist/embedding-fallback.js +2 -2
  130. package/dist/enrichment/index.d.ts +1 -1
  131. package/dist/entity-retrieval.d.ts +1 -1
  132. package/dist/entity-retrieval.js +5 -5
  133. package/dist/entity-schema.d.ts +1 -1
  134. package/dist/explicit-capture.d.ts +4 -4
  135. package/dist/explicit-capture.js +2 -2
  136. package/dist/extraction-faithfulness.d.ts +1 -1
  137. package/dist/extraction-judge-telemetry.d.ts +1 -1
  138. package/dist/extraction-judge-training.d.ts +1 -1
  139. package/dist/extraction-judge.d.ts +1 -1
  140. package/dist/extraction.d.ts +1 -1
  141. package/dist/extraction.js +4 -4
  142. package/dist/fallback-llm.d.ts +1 -1
  143. package/dist/graph-dashboard-diff.d.ts +1 -1
  144. package/dist/graph-dashboard-key.d.ts +1 -1
  145. package/dist/graph-dashboard-parser.d.ts +1 -1
  146. package/dist/graph-edge-reinforcement.d.ts +1 -1
  147. package/dist/graph-snapshot.d.ts +1 -1
  148. package/dist/graph.d.ts +1 -1
  149. package/dist/identity-continuity.d.ts +1 -1
  150. package/dist/importance.d.ts +1 -1
  151. package/dist/index.d.ts +9 -9
  152. package/dist/index.js +142 -142
  153. package/dist/intent.d.ts +1 -1
  154. package/dist/lcm/engine.d.ts +1 -1
  155. package/dist/lcm/engine.js +3 -3
  156. package/dist/lcm/index.d.ts +1 -1
  157. package/dist/lcm/index.js +8 -8
  158. package/dist/lcm/tools.d.ts +1 -1
  159. package/dist/lifecycle.d.ts +1 -1
  160. package/dist/live-connectors-runner.d.ts +1 -1
  161. package/dist/local-llm.d.ts +1 -1
  162. package/dist/local-llm.js +2 -2
  163. package/dist/local-model-endpoint.d.ts +1 -1
  164. package/dist/maintenance/memory-governance.d.ts +1 -1
  165. package/dist/maintenance/memory-governance.js +4 -4
  166. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  167. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  168. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  169. package/dist/memory-action-policy.d.ts +1 -1
  170. package/dist/memory-cache.d.ts +1 -1
  171. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  172. package/dist/memory-projection-store.d.ts +1 -1
  173. package/dist/memory-provenance.d.ts +1 -1
  174. package/dist/memory-worth-outcomes.d.ts +1 -1
  175. package/dist/models-json.d.ts +1 -1
  176. package/dist/namespaces/migrate.d.ts +2 -2
  177. package/dist/namespaces/migrate.js +10 -10
  178. package/dist/namespaces/principal.d.ts +1 -1
  179. package/dist/namespaces/search.d.ts +1 -1
  180. package/dist/namespaces/search.js +4 -4
  181. package/dist/namespaces/storage.d.ts +2 -2
  182. package/dist/namespaces/storage.js +6 -6
  183. package/dist/native-knowledge.d.ts +1 -1
  184. package/dist/operator-toolkit.d.ts +1 -1
  185. package/dist/operator-toolkit.js +17 -17
  186. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  187. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  188. package/dist/orchestration/maintenance.d.ts +2 -2
  189. package/dist/orchestration/maintenance.js +10 -10
  190. package/dist/{orchestrator-D7bb8ZGp.d.ts → orchestrator-BhxMxtQJ.d.ts} +27 -106
  191. package/dist/orchestrator.d.ts +4 -4
  192. package/dist/orchestrator.js +86 -86
  193. package/dist/patterns-cli.d.ts +1 -1
  194. package/dist/policy-runtime.d.ts +1 -1
  195. package/dist/provenance.d.ts +1 -1
  196. package/dist/qmd-recall-cache.d.ts +1 -1
  197. package/dist/qmd.d.ts +1 -1
  198. package/dist/recall-disclosure-escalation.d.ts +1 -1
  199. package/dist/recall-explain-renderer.d.ts +1 -1
  200. package/dist/recall-planner-llm.d.ts +1 -1
  201. package/dist/recall-state.d.ts +1 -1
  202. package/dist/recall-tag-filter.d.ts +1 -1
  203. package/dist/recall-xray-cli.d.ts +1 -1
  204. package/dist/recall-xray-renderer.d.ts +1 -1
  205. package/dist/recall-xray.d.ts +1 -1
  206. package/dist/resolve-auth-token.d.ts +1 -1
  207. package/dist/resume-bundles.js +1 -1
  208. package/dist/retrieval-agents.d.ts +1 -1
  209. package/dist/retrieval-tiers.d.ts +1 -1
  210. package/dist/routing/engine.d.ts +1 -1
  211. package/dist/routing/store.d.ts +1 -1
  212. package/dist/routing/store.js +2 -2
  213. package/dist/schemas.d.ts +22 -22
  214. package/dist/search/embed-helper.d.ts +1 -1
  215. package/dist/search/embed-helper.js +2 -2
  216. package/dist/search/factory.d.ts +1 -1
  217. package/dist/search/factory.js +3 -3
  218. package/dist/search/index.d.ts +1 -1
  219. package/dist/search/index.js +3 -3
  220. package/dist/search/lancedb-backend.d.ts +1 -1
  221. package/dist/search/meilisearch-backend.d.ts +1 -1
  222. package/dist/search/noop-backend.d.ts +1 -1
  223. package/dist/search/orama-backend.d.ts +1 -1
  224. package/dist/search/port.d.ts +1 -1
  225. package/dist/search/remote-backend.d.ts +1 -1
  226. package/dist/secure-store/index.js +2 -2
  227. package/dist/{semantic-consolidation-CTfbVwCA.d.ts → semantic-consolidation-BrsoFeMP.d.ts} +1 -1
  228. package/dist/semantic-consolidation.d.ts +2 -2
  229. package/dist/semantic-consolidation.js +7 -7
  230. package/dist/semantic-rule-promotion.js +4 -4
  231. package/dist/semantic-rule-verifier.d.ts +1 -1
  232. package/dist/semantic-rule-verifier.js +4 -4
  233. package/dist/session-observer-bands.d.ts +1 -1
  234. package/dist/session-observer-state.d.ts +1 -1
  235. package/dist/shared-context/manager.d.ts +1 -1
  236. package/dist/shared-context/manager.js +2 -2
  237. package/dist/signal.d.ts +1 -1
  238. package/dist/storage.d.ts +1 -1
  239. package/dist/storage.js +3 -3
  240. package/dist/summarizer.d.ts +1 -1
  241. package/dist/summarizer.js +3 -3
  242. package/dist/summary-snapshot.d.ts +1 -1
  243. package/dist/temporal-supersession.d.ts +1 -1
  244. package/dist/temporal-validity.d.ts +1 -1
  245. package/dist/threading.d.ts +1 -1
  246. package/dist/tier-migration.d.ts +1 -1
  247. package/dist/tier-routing.d.ts +1 -1
  248. package/dist/topics.d.ts +1 -1
  249. package/dist/transcript.d.ts +1 -1
  250. package/dist/transfer/backup.js +3 -3
  251. package/dist/transfer/capsule-export.js +3 -3
  252. package/dist/transfer/capsule-import.js +3 -3
  253. package/dist/transfer/types.d.ts +12 -12
  254. package/dist/trust-score-stage.d.ts +1 -1
  255. package/dist/trust-score.d.ts +1 -1
  256. package/dist/{types-D4NYDtXI.d.ts → types-BfI4uJeb.d.ts} +1 -1
  257. package/dist/types.d.ts +1 -1
  258. package/dist/utility-runtime.d.ts +1 -1
  259. package/dist/verified-recall.js +4 -4
  260. package/package.json +2 -2
  261. package/src/access-recall-surface.ts +1552 -0
  262. package/src/access-service.ts +29 -1219
  263. package/src/orchestration/recall-entry.ts +290 -0
  264. package/src/orchestration/session-context.ts +407 -0
  265. package/src/orchestrator.ts +125 -462
  266. package/dist/chunk-BJMBJZ2Y.js.map +0 -1
  267. package/dist/chunk-RWZB6MAY.js.map +0 -1
  268. package/dist/chunk-UETBEHBA.js.map +0 -1
  269. /package/dist/{capsule-crypto-7FJQINUR.js.map → access-recall-surface.js.map} +0 -0
  270. /package/dist/{contradiction-scan-3FJYWS3G.js.map → capsule-crypto-CZJSLEFG.js.map} +0 -0
  271. /package/dist/{chunk-2NLLXCJG.js.map → chunk-2BFLDH5F.js.map} +0 -0
  272. /package/dist/{chunk-UU32EAIY.js.map → chunk-2DPCAW2T.js.map} +0 -0
  273. /package/dist/{chunk-5PZCDUJ6.js.map → chunk-2U4E34Y3.js.map} +0 -0
  274. /package/dist/{chunk-JRZV3767.js.map → chunk-33NX5BLR.js.map} +0 -0
  275. /package/dist/{chunk-X7Y7WX73.js.map → chunk-437S3G37.js.map} +0 -0
  276. /package/dist/{chunk-HAZVVOYT.js.map → chunk-5DXUE7GB.js.map} +0 -0
  277. /package/dist/{chunk-4DLFJJOQ.js.map → chunk-5JAT4ZJD.js.map} +0 -0
  278. /package/dist/{chunk-RS25QOKZ.js.map → chunk-64SE4X5G.js.map} +0 -0
  279. /package/dist/{chunk-QRRBXD24.js.map → chunk-67JRXV6Z.js.map} +0 -0
  280. /package/dist/{chunk-WA7FECSG.js.map → chunk-6ROCH5JV.js.map} +0 -0
  281. /package/dist/{chunk-5QANTTY2.js.map → chunk-BIAMJUUJ.js.map} +0 -0
  282. /package/dist/{chunk-IKHKHADN.js.map → chunk-C4LNKQ3M.js.map} +0 -0
  283. /package/dist/{chunk-B35L45HZ.js.map → chunk-DC5OBZS3.js.map} +0 -0
  284. /package/dist/{chunk-ORAK3LNZ.js.map → chunk-ER3CCWK2.js.map} +0 -0
  285. /package/dist/{chunk-SD34EK4Z.js.map → chunk-FEELYTRT.js.map} +0 -0
  286. /package/dist/{chunk-KQAFEZQX.js.map → chunk-FLVL4L3L.js.map} +0 -0
  287. /package/dist/{chunk-WIWPSQYU.js.map → chunk-FXRFFYWD.js.map} +0 -0
  288. /package/dist/{chunk-W3M34XT4.js.map → chunk-GECEETQ3.js.map} +0 -0
  289. /package/dist/{chunk-WWSXVOGY.js.map → chunk-GFGYHLON.js.map} +0 -0
  290. /package/dist/{chunk-WOX2UW4K.js.map → chunk-IBPEVGP5.js.map} +0 -0
  291. /package/dist/{chunk-JZ7P26GF.js.map → chunk-IQWFWSCP.js.map} +0 -0
  292. /package/dist/{chunk-6SXNQFHB.js.map → chunk-J2JB6BHT.js.map} +0 -0
  293. /package/dist/{chunk-K2JM4DZZ.js.map → chunk-JHZ3LEM7.js.map} +0 -0
  294. /package/dist/{chunk-YGGXUNS4.js.map → chunk-JNKSFIAY.js.map} +0 -0
  295. /package/dist/{chunk-CGZWGVQF.js.map → chunk-K5CWCJKJ.js.map} +0 -0
  296. /package/dist/{chunk-TPLE6SNY.js.map → chunk-KFNG72W7.js.map} +0 -0
  297. /package/dist/{chunk-4NWIGAIC.js.map → chunk-KT2VQLP7.js.map} +0 -0
  298. /package/dist/{chunk-6FNZ3NMS.js.map → chunk-L3VSTJ2V.js.map} +0 -0
  299. /package/dist/{chunk-ARV3AUOM.js.map → chunk-M5QKGHCR.js.map} +0 -0
  300. /package/dist/{chunk-E4PFDD3N.js.map → chunk-MEPWWSTD.js.map} +0 -0
  301. /package/dist/{chunk-V5RVMULT.js.map → chunk-MLDAD27I.js.map} +0 -0
  302. /package/dist/{chunk-K2R3DEEH.js.map → chunk-MTN2G4DT.js.map} +0 -0
  303. /package/dist/{chunk-DBSMLRTO.js.map → chunk-MW7UUXTA.js.map} +0 -0
  304. /package/dist/{chunk-OSMBC3JU.js.map → chunk-OKOF7ITI.js.map} +0 -0
  305. /package/dist/{chunk-DBRVU5PR.js.map → chunk-OVMLS7TN.js.map} +0 -0
  306. /package/dist/{chunk-NNL4LJAD.js.map → chunk-QMMCMV2L.js.map} +0 -0
  307. /package/dist/{chunk-RNWWAS5V.js.map → chunk-QRM3K3XT.js.map} +0 -0
  308. /package/dist/{chunk-HM6UJPPO.js.map → chunk-R7TGDLV3.js.map} +0 -0
  309. /package/dist/{chunk-EEKDRAWX.js.map → chunk-RBYV3WSB.js.map} +0 -0
  310. /package/dist/{chunk-LOPZ2DWR.js.map → chunk-RRRZ3CLN.js.map} +0 -0
  311. /package/dist/{chunk-7TWA7DKP.js.map → chunk-S5UYWK3O.js.map} +0 -0
  312. /package/dist/{chunk-QOGXUNWL.js.map → chunk-SC5AEZLU.js.map} +0 -0
  313. /package/dist/{chunk-SNEXVX3U.js.map → chunk-UFNQKZV7.js.map} +0 -0
  314. /package/dist/{chunk-W3BFT5OJ.js.map → chunk-V4ZPLQLC.js.map} +0 -0
  315. /package/dist/{chunk-SQNED75R.js.map → chunk-V5UV7BOA.js.map} +0 -0
  316. /package/dist/{chunk-K5LLP4CY.js.map → chunk-XKAOT4ZB.js.map} +0 -0
  317. /package/dist/{chunk-ZBVQ4ZM4.js.map → chunk-YGNQG4Q6.js.map} +0 -0
  318. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  319. /package/dist/{chunk-HYI2GP3F.js.map → chunk-ZGGI3COG.js.map} +0 -0
@@ -251,6 +251,8 @@ import {
251
251
  import { formatProfileTraceAscii } from "./profiling.js";
252
252
  import { resolveAccessSetupCapabilities, resolveGraphConstructionCapabilities, resolveIndexingCapabilities } from "./capabilities.js";
253
253
  import { resolveRecallEnhancementCapabilities } from "./capabilities.js";
254
+ import { AccessRecallSurface } from "./access-recall-surface.js";
255
+ import { selfDeps } from "./orchestration/self-deps.js";
254
256
 
255
257
  export class EngramAccessInputError extends Error {}
256
258
 
@@ -1305,6 +1307,18 @@ export class EngramAccessService {
1305
1307
  private readonly budget: CrossNamespaceBudget;
1306
1308
  private readonly auditAdapter: AccessAuditAdapter | null;
1307
1309
 
1310
+ /** AccessRecallSurface (access-service decomposition). Lazy; selfDeps live wiring. */
1311
+ private _accessRecallSurface: AccessRecallSurface | undefined;
1312
+
1313
+ private get accessRecallSurface(): AccessRecallSurface {
1314
+ if (!this._accessRecallSurface) {
1315
+ this._accessRecallSurface = new AccessRecallSurface(
1316
+ selfDeps<ConstructorParameters<typeof AccessRecallSurface>[0]>(this),
1317
+ );
1318
+ }
1319
+ return this._accessRecallSurface;
1320
+ }
1321
+
1308
1322
  constructor(private readonly orchestrator: Orchestrator) {
1309
1323
  this.idempotency = new AccessIdempotencyStore(orchestrator.config.memoryDir);
1310
1324
  const accessCaps = resolveAccessSetupCapabilities(orchestrator.config); // #1566 Cluster B
@@ -2097,83 +2111,9 @@ export class EngramAccessService {
2097
2111
  */
2098
2112
  rawExcerptsSuppressed?: boolean;
2099
2113
  }): Promise<EngramAccessRecallResponse> {
2100
- const memoryIds = options.snapshot.results.map((result) => result.memoryId);
2101
- const resultPaths = options.snapshot.results.map((result) => result.path);
2102
- const namespace = options.snapshot.namespace
2103
- ? this.resolveNamespace(options.snapshot.namespace)
2104
- : this.orchestrator.config.defaultNamespace;
2105
- const sourcesUsed = Array.from(
2106
- new Set(options.snapshot.results.map((result) => result.servedBy)),
2107
- );
2108
- const snapshotForSerialization: LastRecallSnapshot = {
2109
- sessionKey: options.sessionKey ?? "",
2110
- recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
2111
- queryHash: createHash("sha256").update(options.query).digest("hex"),
2112
- queryLen: options.query.length,
2113
- memoryIds,
2114
- namespace,
2115
- recallNamespaces: [namespace],
2116
- traceId: options.snapshot.traceId,
2117
- plannerMode: options.normalizedMode,
2118
- requestedMode:
2119
- options.requestedMode && options.requestedMode !== "auto"
2120
- ? options.requestedMode
2121
- : undefined,
2122
- sourcesUsed,
2123
- budgetsApplied: {
2124
- appliedTopK: memoryIds.length,
2125
- recallBudgetChars: options.snapshot.budget.chars,
2126
- maxMemoryTokens: this.orchestrator.config.maxMemoryTokens,
2127
- finalContextChars: options.snapshot.budget.used,
2128
- },
2129
- latencyMs: Date.now() - options.startedAt,
2130
- resultPaths,
2131
- };
2132
- const results = await this.serializeRecallResults(
2133
- snapshotForSerialization,
2134
- options.disclosure,
2135
- {
2136
- query: options.query,
2137
- ...(options.sessionKey ? { sessionKey: options.sessionKey } : {}),
2138
- ...(options.rawExcerptNamespace
2139
- ? { rawExcerptNamespace: options.rawExcerptNamespace }
2140
- : {}),
2141
- ...(options.rawExcerptSessionIds !== undefined
2142
- ? { rawExcerptSessionIds: options.rawExcerptSessionIds }
2143
- : {}),
2144
- ...(options.rawExcerptsSuppressed
2145
- ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed }
2146
- : {}),
2147
- },
2114
+ return this.accessRecallSurface.buildRecallResponseFromXraySnapshot(
2115
+ options,
2148
2116
  );
2149
- const context = results
2150
- .map((result) => {
2151
- const content =
2152
- typeof result.content === "string" && result.content.length > 0
2153
- ? result.content
2154
- : "";
2155
- return content || result.preview;
2156
- })
2157
- .filter((text) => text.length > 0)
2158
- .join("\n\n");
2159
-
2160
- return {
2161
- query: options.query,
2162
- ...(options.sessionKey ? { sessionKey: options.sessionKey } : {}),
2163
- namespace,
2164
- context,
2165
- count: memoryIds.length,
2166
- memoryIds,
2167
- results,
2168
- recordedAt: snapshotForSerialization.recordedAt,
2169
- traceId: options.snapshot.traceId,
2170
- plannerMode: options.normalizedMode,
2171
- fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
2172
- sourcesUsed,
2173
- disclosure: options.disclosure,
2174
- budgetsApplied: snapshotForSerialization.budgetsApplied,
2175
- latencyMs: snapshotForSerialization.latencyMs,
2176
- };
2177
2117
  }
2178
2118
 
2179
2119
  private async serializeRecallResults(
@@ -2214,198 +2154,11 @@ export class EngramAccessService {
2214
2154
  }
2215
2155
  | null = null,
2216
2156
  ): Promise<EngramAccessMemorySummary[]> {
2217
- if (!snapshot) return [];
2218
- const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
2219
- const storage = await this.orchestrator.getStorage(namespace);
2220
- const storageDir = storage.dir;
2221
- const recallNamespaces = Array.from(
2222
- new Set(
2223
- [
2224
- namespace,
2225
- ...(Array.isArray(snapshot.recallNamespaces)
2226
- ? snapshot.recallNamespaces.map((ns) => this.resolveNamespace(ns))
2227
- : []),
2228
- this.orchestrator.config.defaultNamespace,
2229
- this.orchestrator.config.sharedNamespace,
2230
- ...(this.orchestrator.config.namespacePolicies ?? []).map((p) => p.name),
2231
- ].filter((ns): ns is string => typeof ns === "string" && ns.length > 0),
2232
- ),
2157
+ return this.accessRecallSurface.serializeRecallResults(
2158
+ snapshot,
2159
+ disclosure,
2160
+ rawContext,
2233
2161
  );
2234
- const results: EngramAccessMemorySummary[] = [];
2235
- const seen = new Set<string>();
2236
- const collectionNamespaceFromPrefix = (collectionPrefix: string): string | null => {
2237
- const baseCollection = this.orchestrator.config.qmdCollection;
2238
- if (collectionPrefix === baseCollection) return this.orchestrator.config.defaultNamespace;
2239
- const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`)
2240
- ? collectionPrefix.slice(baseCollection.length + 2)
2241
- : "";
2242
- if (!namespaceSuffix) return null;
2243
-
2244
- const decoded = namespaceIdentityFromToken(namespaceSuffix);
2245
- if (decoded !== null) return decoded || this.orchestrator.config.defaultNamespace;
2246
- if (namespaceSuffix.startsWith("ns--")) {
2247
- const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
2248
- return legacyNamespace || null;
2249
- }
2250
- return null;
2251
- };
2252
- const readResultPath = async (
2253
- memoryPath: string,
2254
- ): Promise<{ memory: MemoryFile; baseDir: string } | null> => {
2255
- const parts = qmdCollectionPathParts(memoryPath);
2256
- const coldCollection =
2257
- this.orchestrator.config.qmdColdCollection ?? "openclaw-engram-cold";
2258
- if (parts && parts.collection === coldCollection) {
2259
- const storages: Array<{ storage: StorageManager; dir: string }> = [];
2260
- const seenStorageDirs = new Set<string>();
2261
- const addStorage = (candidateStorage: StorageManager): void => {
2262
- const candidateDir = nodePath.resolve(candidateStorage.dir);
2263
- if (seenStorageDirs.has(candidateDir)) return;
2264
- seenStorageDirs.add(candidateDir);
2265
- storages.push({ storage: candidateStorage, dir: candidateDir });
2266
- };
2267
- addStorage(storage);
2268
- for (const recallNamespace of recallNamespaces) {
2269
- try {
2270
- addStorage(await this.orchestrator.getStorage(recallNamespace));
2271
- } catch {
2272
- continue;
2273
- }
2274
- }
2275
- for (const candidateStorage of storages) {
2276
- try {
2277
- const coldRoot = nodePath.join(candidateStorage.dir, "cold");
2278
- for (const candidatePath of qmdResultPathCandidates(
2279
- coldRoot,
2280
- parts.relativePath,
2281
- )) {
2282
- const memory =
2283
- await candidateStorage.storage.readMemoryByPath(candidatePath);
2284
- if (memory) return { memory, baseDir: candidateStorage.dir };
2285
- }
2286
- } catch (err) {
2287
- if (err instanceof SecureStoreLockedError) throw err;
2288
- continue;
2289
- }
2290
- }
2291
- return null;
2292
- }
2293
-
2294
- const collectionNamespace = parts
2295
- ? collectionNamespaceFromPrefix(parts.collection)
2296
- : null;
2297
-
2298
- if (parts && collectionNamespace) {
2299
- try {
2300
- const collectionStorage =
2301
- await this.orchestrator.getStorage(collectionNamespace);
2302
- for (const candidate of qmdResultPathCandidates(
2303
- collectionStorage.dir,
2304
- parts.relativePath,
2305
- )) {
2306
- const memory = await collectionStorage.readMemoryByPath(candidate);
2307
- if (memory) return { memory, baseDir: collectionStorage.dir };
2308
- }
2309
- return null;
2310
- } catch (err) {
2311
- if (err instanceof SecureStoreLockedError) throw err;
2312
- return null;
2313
- }
2314
- }
2315
-
2316
- if (nodePath.isAbsolute(memoryPath)) {
2317
- const ownerStorage = await this.storageForAbsoluteRecallPath(
2318
- memoryPath,
2319
- namespace,
2320
- recallNamespaces,
2321
- );
2322
- if (!ownerStorage) return null;
2323
- for (const candidate of qmdResultPathCandidates(
2324
- ownerStorage.dir,
2325
- memoryPath,
2326
- )) {
2327
- const memory = await ownerStorage.storage.readMemoryByPath(candidate);
2328
- if (memory) return { memory, baseDir: ownerStorage.dir };
2329
- }
2330
- return null;
2331
- }
2332
-
2333
- for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
2334
- const memory = await storage.readMemoryByPath(candidate);
2335
- if (memory) return { memory, baseDir: storageDir };
2336
- }
2337
- return null;
2338
- };
2339
-
2340
- // Pre-fetch raw excerpts once when `disclosure === "raw"` so we don't
2341
- // hit the LCM archive per-result (issue #677 PR 2/4). Excerpts are
2342
- // attached to the first result; per-result attribution is reserved
2343
- // for a future PR if/when the LCM index can be joined to memory ids.
2344
- // Coerce `null` (non-raw disclosure) to `undefined` so the optional
2345
- // serializer field is never explicitly `null`.
2346
- // Namespace for the LCM `${namespace}:${sessionKey}` prefix: prefer the
2347
- // caller-supplied READ-AUTHORIZATION-GATED `rawExcerptNamespace` (#1505
2348
- // thread 2f7) so raw disclosure honours the same read gate as normal recall
2349
- // + `lcmSearch` and never attaches `<principal>-project-*` overlay rows the
2350
- // gate excludes. Fall back to the snapshot's resolved namespace only when no
2351
- // gated namespace was threaded (sessionless / legacy callers) — unchanged.
2352
- const rawExcerptsResult =
2353
- rawContext?.rawExcerptsSuppressed === true
2354
- ? // Implicit raw recall with NO readable LCM namespace (#1505 thread
2355
- // NBHWz): emit empty excerpts rather than falling back to the
2356
- // write/overlay `namespace` the read gate excludes.
2357
- []
2358
- : await this.fetchRawExcerpts(
2359
- disclosure,
2360
- rawContext
2361
- ? {
2362
- query: rawContext.query,
2363
- ...(rawContext.sessionKey
2364
- ? { sessionKey: rawContext.sessionKey }
2365
- : {}),
2366
- namespace: rawContext.rawExcerptNamespace ?? namespace,
2367
- ...(rawContext.rawExcerptSessionIds !== undefined
2368
- ? { lcmSessionIds: rawContext.rawExcerptSessionIds }
2369
- : {}),
2370
- }
2371
- : null,
2372
- );
2373
- const rawExcerpts = rawExcerptsResult ?? undefined;
2374
-
2375
- for (const memoryPath of snapshot.resultPaths ?? []) {
2376
- if (!memoryPath || seen.has(memoryPath)) continue;
2377
- const resolved = await readResultPath(memoryPath);
2378
- if (!resolved) continue;
2379
- const { memory, baseDir } = resolved;
2380
- seen.add(memoryPath);
2381
- results.push(
2382
- this.serializeMemorySummary(
2383
- memory,
2384
- baseDir,
2385
- disclosure,
2386
- // Attach the (possibly empty) raw excerpts to the first raw
2387
- // result; subsequent results do not duplicate the array.
2388
- results.length === 0 ? rawExcerpts : undefined,
2389
- ),
2390
- );
2391
- }
2392
-
2393
- if (results.length > 0) return results;
2394
-
2395
- for (const memoryId of snapshot.memoryIds) {
2396
- const memory = await storage.getMemoryById(memoryId);
2397
- if (!memory || seen.has(memory.path)) continue;
2398
- seen.add(memory.path);
2399
- results.push(
2400
- this.serializeMemorySummary(
2401
- memory,
2402
- storageDir,
2403
- disclosure,
2404
- results.length === 0 ? rawExcerpts : undefined,
2405
- ),
2406
- );
2407
- }
2408
- return results;
2409
2162
  }
2410
2163
 
2411
2164
  private async storageForAbsoluteRecallPath(
@@ -2460,19 +2213,6 @@ export class EngramAccessService {
2460
2213
  return matches[0] ?? null;
2461
2214
  }
2462
2215
 
2463
- /**
2464
- * Fetch raw transcript excerpts from the LCM archive for `disclosure ===
2465
- * "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
2466
- * depths, an empty array when LCM is disabled / not initialized / has no
2467
- * matches, and an array of LCM-side excerpts otherwise. Errors are
2468
- * swallowed and treated as "no excerpts" so a failing LCM never breaks
2469
- * the recall response.
2470
- *
2471
- * Namespace handling: LCM archival prefixes non-default-namespace
2472
- * sessions with `${namespace}:${sessionKey}` (see `observe()` around
2473
- * line 2498), so the lookup must mirror that prefix or raw recalls in
2474
- * non-default namespaces miss their own excerpts.
2475
- */
2476
2216
  private async fetchRawExcerpts(
2477
2217
  disclosure: RecallDisclosure,
2478
2218
  context: {
@@ -2493,63 +2233,10 @@ export class EngramAccessService {
2493
2233
  lcmSessionIds?: string[];
2494
2234
  } | null,
2495
2235
  ): Promise<EngramAccessMemorySummary["rawExcerpts"] | null> {
2496
- if (disclosure !== "raw") return null;
2497
- if (!context || !context.query) return [];
2498
- // Privacy guard: raw disclosure must be session-scoped. Without a
2499
- // sessionKey, `lcm.searchContextFull(query, n, undefined)` searches
2500
- // across every archived session in the LCM store and would return
2501
- // excerpts from unrelated sessions (potentially crossing namespaces
2502
- // via their `${namespace}:${sessionKey}` prefix encoding). Treat a
2503
- // missing sessionKey as "no excerpts" — callers asking for raw
2504
- // disclosure outside a session get an empty list, not a leak.
2505
- if (!context.sessionKey) return [];
2506
- const lcm = this.orchestrator.lcmEngine;
2507
- if (!lcm || !lcm.enabled) return [];
2508
- try {
2509
- const legacyKey =
2510
- context.namespace &&
2511
- context.namespace !== this.orchestrator.config.defaultNamespace
2512
- ? `${context.namespace}:${context.sessionKey}`
2513
- : context.sessionKey;
2514
- const lcmSessionIds =
2515
- context.lcmSessionIds !== undefined
2516
- ? context.lcmSessionIds
2517
- : [legacyKey];
2518
- // Cap the excerpt fanout so recall responses stay bounded. Five matches
2519
- // is enough to anchor the model in the raw transcript without ballooning
2520
- // token spend; raw is meant as the escape hatch, not the default. The cap
2521
- // is applied across the MERGED result set so adding fallback keys never
2522
- // inflates the excerpt budget.
2523
- const limit = 5;
2524
- const seenRows = new Set<string>();
2525
- const excerpts: NonNullable<EngramAccessMemorySummary["rawExcerpts"]> = [];
2526
- const settledRows = await Promise.allSettled(
2527
- lcmSessionIds.map(async (lcmSessionKey) =>
2528
- lcm.searchContextFull(context.query, limit, lcmSessionKey),
2529
- ),
2530
- );
2531
- for (const result of settledRows) {
2532
- if (excerpts.length >= limit) break;
2533
- if (result.status !== "fulfilled") continue;
2534
- for (const r of result.value) {
2535
- const dedupeKey = `${r.session_id} ${r.turn_index}`;
2536
- if (seenRows.has(dedupeKey)) continue;
2537
- seenRows.add(dedupeKey);
2538
- excerpts.push({
2539
- turnIndex: r.turn_index,
2540
- role: r.role,
2541
- content: r.content,
2542
- sessionId: r.session_id,
2543
- });
2544
- if (excerpts.length >= limit) break;
2545
- }
2546
- }
2547
- return excerpts;
2548
- } catch {
2549
- // CLAUDE.md rule 13: never let an external subsystem (LCM/SQLite)
2550
- // crash the primary recall flow.
2551
- return [];
2552
- }
2236
+ return this.accessRecallSurface.fetchRawExcerpts(
2237
+ disclosure,
2238
+ context,
2239
+ );
2553
2240
  }
2554
2241
 
2555
2242
  private async handleIdempotentWrite<T extends { idempotencyReplay?: boolean }>(options: {
@@ -3267,453 +2954,9 @@ export class EngramAccessService {
3267
2954
  response: EngramAccessRecallResponse;
3268
2955
  budgetRecordPrincipal: string | null;
3269
2956
  }> {
3270
- const query = request.query;
3271
- // Disclosure depth (issue #677). Default to `"chunk"` when omitted so
3272
- // pre-#677 callers see unchanged behavior. Reject explicitly invalid
3273
- // string values per CLAUDE.md rule 51 (do not silently fall back).
3274
- const callerProvidedDisclosure =
3275
- request.disclosure !== undefined && request.disclosure !== null;
3276
- const requestedDisclosure: RecallDisclosure = (() => {
3277
- if (!callerProvidedDisclosure) {
3278
- return DEFAULT_RECALL_DISCLOSURE;
3279
- }
3280
- if (!isRecallDisclosure(request.disclosure)) {
3281
- throw new EngramAccessInputError(
3282
- `disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`,
3283
- );
3284
- }
3285
- return request.disclosure;
3286
- })();
3287
- // Attach any coding context shipped with the recall request BEFORE
3288
- // namespace resolution so the overlay applies to this recall (issue #569).
3289
- if (request.codingContext !== undefined && request.sessionKey) {
3290
- this.setCodingContext({
3291
- sessionKey: request.sessionKey,
3292
- codingContext: request.codingContext,
3293
- });
3294
- }
3295
- // Auto-resolve coding context from cwd/projectTag when no explicit
3296
- // codingContext was supplied (issue #569 wiring). This allows Claude
3297
- // Code hooks and OpenClaw connectors to get project-scoped memory
3298
- // transparently.
3299
- if (request.codingContext === undefined && request.sessionKey) {
3300
- await this.maybeAttachCodingContext(request.sessionKey, {
3301
- cwd: request.cwd,
3302
- projectTag: request.projectTag,
3303
- });
3304
- }
3305
- const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
3306
- const namespaceOverride = this.resolveRecallNamespace(
3307
- request.namespace,
3308
- request.sessionKey,
3309
- authenticatedPrincipal,
3310
- );
3311
- const namespace = namespaceOverride ?? this.orchestrator.config.defaultNamespace;
3312
- // Normalize mode early so that no_recall / invalid modes skip budget
3313
- // accounting (Codex P1: budget recorded before mode validation).
3314
- const mode = this.normalizeRecallMode(request.mode);
3315
- const maybePrincipal = this.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
3316
- if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !maybePrincipal) {
3317
- throw new EngramAccessInputError(
3318
- "authentication required: namespaces are enabled and no principal was supplied",
3319
- );
3320
- }
3321
- const principal = maybePrincipal ?? "default";
3322
- const principalNamespace = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
3323
- const profileCodingContext =
3324
- request.sessionKey && typeof this.orchestrator.getCodingContextForSession === "function"
3325
- ? this.orchestrator.getCodingContextForSession(request.sessionKey)
3326
- : null;
3327
- const profileCodingOverlay =
3328
- !namespaceOverride &&
3329
- profileCodingContext &&
3330
- resolveNamespaceCapabilities(this.orchestrator.config).namespaces &&
3331
- this.orchestrator.config.codingMode?.projectScope
3332
- ? resolveCodingNamespaceOverlay(
3333
- profileCodingContext,
3334
- this.orchestrator.config.codingMode,
3335
- this.orchestrator.config.defaultNamespace,
3336
- )
3337
- : null;
3338
- const profilePlan = namespaceOverride
3339
- ? null
3340
- : resolveScopeProfilePlan({
3341
- config: this.orchestrator.config,
3342
- principal,
3343
- codingContext: profileCodingContext,
3344
- codingOverlay: profileCodingOverlay,
3345
- });
3346
- // Skip budget checks for modes that never perform a cross-namespace read.
3347
- const modeSkipsBudget = mode === "no_recall";
3348
- // Derive the full set of namespaces the orchestrator will actually search.
3349
- // When no explicit override is provided, `recallNamespacesForPrincipal()` may
3350
- // expand to shared / policy-default namespaces. Budget must be checked
3351
- // against every cross-namespace entry in the effective set so that omitting
3352
- // `namespace` cannot bypass the limiter (Cursor/Codex review feedback).
3353
- //
3354
- const legacyRecallNamespaces = Array.isArray(this.orchestrator.config.defaultRecallNamespaces)
3355
- ? recallNamespacesForPrincipal(principal, this.orchestrator.config)
3356
- : [];
3357
- const effectiveNamespaces = namespaceOverride
3358
- ? [namespaceOverride]
3359
- : profilePlan
3360
- ? expandScopeProfileReadNamespaces({
3361
- profilePlan,
3362
- principalSelfNamespace: profilePlan.baseNamespace,
3363
- config: this.orchestrator.config,
3364
- principal,
3365
- codingOverlay: profileCodingOverlay,
3366
- legacyRecallNamespaces,
3367
- })
3368
- : legacyRecallNamespaces;
3369
- const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
3370
- let budgetDecision: BudgetDecision;
3371
- let recordBudgetAfterSuccess = false;
3372
- if (modeSkipsBudget) {
3373
- budgetDecision = {
3374
- allowed: true as const,
3375
- reason: "allowed-same-namespace" as const,
3376
- count: 0,
3377
- limit: {
3378
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
3379
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
3380
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 60_000,
3381
- },
3382
- };
3383
- } else {
3384
- // Peek at every effective namespace to determine whether ANY would be
3385
- // cross-namespace WITHOUT recording side effects (Cursor review:
3386
- // multi-count bug). Record a single budget event only when at least
3387
- // one effective namespace differs from the principal's self namespace,
3388
- // and only after recall succeeds so retried transient failures do not
3389
- // consume budget multiple times before a successful response can be
3390
- // cached behind the request idempotency key.
3391
- let anyCrossNamespace = false;
3392
- let denied: BudgetDecision | null = null;
3393
- let crossNamespaceDecision: BudgetDecision | null = null;
3394
- for (const ns of effectiveNamespaces) {
3395
- const peek = this.budget.peek({
3396
- principal,
3397
- principalNamespace: budgetPrincipalNamespace,
3398
- queryNamespace: ns,
3399
- });
3400
- if (peek.reason !== "allowed-same-namespace") {
3401
- anyCrossNamespace = true;
3402
- crossNamespaceDecision ??= peek;
3403
- }
3404
- if (!peek.allowed) {
3405
- denied = peek;
3406
- break;
3407
- }
3408
- }
3409
- if (denied) {
3410
- // The peek projected a denial — deny without recording so the
3411
- // bucket is not inflated by rejected attempts.
3412
- budgetDecision = denied;
3413
- } else if (anyCrossNamespace) {
3414
- budgetDecision = crossNamespaceDecision ?? {
3415
- allowed: true as const,
3416
- reason: "allowed-under-soft" as const,
3417
- count: 0,
3418
- limit: {
3419
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
3420
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
3421
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 60_000,
3422
- },
3423
- };
3424
- recordBudgetAfterSuccess = true;
3425
- } else {
3426
- budgetDecision = {
3427
- allowed: true as const,
3428
- reason: "allowed-same-namespace" as const,
3429
- count: 0,
3430
- limit: {
3431
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
3432
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
3433
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 60_000,
3434
- },
3435
- };
3436
- }
3437
- if (!budgetDecision.allowed) {
3438
- throw new EngramAccessInputError(
3439
- `recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`,
3440
- );
3441
- }
3442
- // Prune expired principal buckets to prevent unbounded Map growth from
3443
- // high-cardinality / transient principals (Codex P2 review feedback).
3444
- this.budget.gc();
3445
- }
3446
- const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : undefined;
3447
- // Issue #680 — historical recall pin. Validate at the input
3448
- // boundary so a malformed `asOf` is rejected with a structured
3449
- // 400 instead of silently flooring at NaN inside the orchestrator
3450
- // (CLAUDE.md rule 51, gotcha #51). Empty / undefined is fine —
3451
- // means "no pin".
3452
- let asOf: string | undefined;
3453
- if (request.asOf !== undefined && request.asOf !== null) {
3454
- if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
3455
- throw new EngramAccessInputError(
3456
- "asOf must be a non-empty ISO 8601 timestamp string",
3457
- );
3458
- }
3459
- const parsed = Date.parse(request.asOf);
3460
- if (!Number.isFinite(parsed)) {
3461
- throw new EngramAccessInputError(
3462
- `asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`,
3463
- );
3464
- }
3465
- asOf = request.asOf;
3466
- }
3467
- const recallOptions: RecallInvocationOptions = {
3468
- namespace: namespaceOverride,
3469
- topK,
3470
- mode,
3471
- ...(authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {}),
3472
- ...(asOf !== undefined ? { asOf } : {}),
3473
- ...(request.includeLowConfidence === true ? { includeLowConfidence: true } : {}),
3474
- };
3475
- const startedAt = Date.now();
3476
- const context = await this.orchestrator.recall(query, request.sessionKey, recallOptions);
3477
- const snapshot = request.sessionKey
3478
- ? this.orchestrator.lastRecall.get(request.sessionKey)
3479
- : null;
3480
- const effectiveNamespace = snapshot?.namespace
3481
- ? this.resolveNamespace(snapshot.namespace)
3482
- : namespace;
3483
- // Auto-escalation policy (issue #677 PR 4/4). When the operator
3484
- // configured `recallDisclosureEscalation: "auto"` AND the caller
3485
- // did not explicitly choose a disclosure level AND recall produced
3486
- // a low-confidence result set (proxied by fill ratio: results
3487
- // returned / topK requested), we escalate the default `chunk`
3488
- // shape to `section` so the LLM gets richer context to compensate
3489
- // for ambiguous retrieval. Manual mode and explicit caller
3490
- // disclosure both bypass the policy. Documented in
3491
- // `recall-disclosure-escalation.ts` and unit-tested there.
3492
- // Confidence-proxy denominator: priority order is
3493
- // 1. `snapshot.budgetsApplied.appliedTopK` (ALWAYS wins) — this is
3494
- // the limit the orchestrator actually applied after planner /
3495
- // minimal-mode / section-cap narrowing. Codex P1 rounds 2+3
3496
- // on #705 emphasize that even a caller's explicit `request.topK`
3497
- // is wrong when the orchestrator caps below it (e.g. topK=50
3498
- // but appliedTopK=3 makes a 2-hit recall actually 0.67, not
3499
- // 0.04).
3500
- // 2. The caller's explicit `topK` when the snapshot lacks
3501
- // `budgetsApplied` (early-return paths, error cases).
3502
- // 3. Config `qmdMaxResults` as a last-resort fallback.
3503
- // Floor at observed-results so the ratio stays in [0, 1] even if
3504
- // any of the signals drifts below the actual hit count.
3505
- const resultsReturned = snapshot?.memoryIds?.length ?? 0;
3506
- const appliedTopK = snapshot?.budgetsApplied?.appliedTopK;
3507
- const configMaxResults =
3508
- typeof this.orchestrator.config.qmdMaxResults === "number" &&
3509
- Number.isFinite(this.orchestrator.config.qmdMaxResults) &&
3510
- this.orchestrator.config.qmdMaxResults > 0
3511
- ? this.orchestrator.config.qmdMaxResults
3512
- : 0;
3513
- const topKDenominator =
3514
- typeof appliedTopK === "number" &&
3515
- Number.isFinite(appliedTopK) &&
3516
- appliedTopK > 0
3517
- ? Math.max(appliedTopK, resultsReturned)
3518
- : typeof topK === "number" && topK > 0
3519
- ? Math.max(topK, resultsReturned)
3520
- : Math.max(configMaxResults, resultsReturned, 1);
3521
- // When the recall produced no snapshot (sessionless / namespace
3522
- // mismatch / early-return path), there is no confidence signal to
3523
- // base escalation on. Pass `undefined` so the helper takes its
3524
- // `no-top-k-confidence` branch instead of computing 0/N=0 and
3525
- // forcing auto-escalation on every sessionless caller (Codex P2
3526
- // review on PR #705).
3527
- const topKConfidence =
3528
- snapshot && topKDenominator > 0
3529
- ? Math.min(1, resultsReturned / topKDenominator)
3530
- : undefined;
3531
- const escalationDecision = decideDisclosureEscalation({
3532
- mode: this.orchestrator.config.recallDisclosureEscalation,
3533
- threshold: this.orchestrator.config.recallDisclosureEscalationThreshold,
3534
- originalDisclosure: requestedDisclosure,
3535
- callerProvidedDisclosure,
3536
- topKConfidence,
3537
- });
3538
- const disclosure = escalationDecision.effective;
3539
- // Gate the raw-excerpt LCM read with the SAME read-authorization namespace
3540
- // `lcmSearch` + the in-prompt LCM sections use (#1505 thread 2f7), so
3541
- // `disclosure: "raw"` never attaches `<principal>-project-*` overlay rows
3542
- // when the principal can WRITE but not READ its self base (or
3543
- // `defaultRecallNamespaces` omits `self`). Computed ONLY for raw disclosure:
3544
- // it is the sole consumer, and resolving the overlay on every chunk/section
3545
- // recall would be wasted work — keeping non-raw recall byte-for-byte
3546
- // unchanged.
3547
- // Trim the sessionKey to match what `orchestrator.recall(...)` already does
3548
- // (`request.sessionKey?.trim() || undefined`) and what the x-ray raw-excerpt
3549
- // path uses (cursor "Raw excerpt key not trimmed"). A whitespace-padded key
3550
- // otherwise drives recall under one identity but resolves the raw-excerpt
3551
- // overlay namespace + LCM `session_id` under a DIFFERENT (untrimmed) prefix,
3552
- // so excerpts are gated/queried inconsistently with recall and the x-ray path.
3553
- const trimmedSessionKey = request.sessionKey?.trim() || undefined;
3554
- const rawExcerptNamespace =
3555
- disclosure === "raw"
3556
- ? this.resolveRawExcerptReadNamespace(
3557
- request.namespace,
3558
- trimmedSessionKey,
3559
- authenticatedPrincipal,
3560
- )
3561
- : undefined;
3562
- // `undefined` for an IMPLICIT raw recall means NO readable LCM namespace
3563
- // exists (restrictive `default` READ policy, no readable overlay/self) —
3564
- // suppress excerpts rather than fall back to the write/overlay namespace the
3565
- // read gate excludes (#1505 thread NBHWz). An EXPLICIT namespace always
3566
- // resolves (or throws) above, so suppression only applies to the implicit
3567
- // path.
3568
- const hasExplicitNamespace =
3569
- typeof request.namespace === "string" &&
3570
- request.namespace.trim().length > 0;
3571
- const rawExcerptsSuppressed =
3572
- disclosure === "raw" &&
3573
- !hasExplicitNamespace &&
3574
- rawExcerptNamespace === undefined;
3575
- // Ordered, read-authorized LCM read key SET (#1505 fallback unification) so
3576
- // raw disclosure finds excerpts a branch-scoped session archived at
3577
- // project/root scope — exactly as recall + `lcmSearch` do. Only with a
3578
- // concrete sessionKey; already read-gated.
3579
- const rawExcerptSessionIds =
3580
- disclosure === "raw" && rawExcerptNamespace && trimmedSessionKey
3581
- ? this.resolveLcmReadSessionIds(
3582
- request.namespace,
3583
- rawExcerptNamespace,
3584
- trimmedSessionKey,
3585
- authenticatedPrincipal,
3586
- )
3587
- : undefined;
3588
- let results = await this.serializeRecallResults(snapshot, disclosure, {
3589
- query,
3590
- sessionKey: trimmedSessionKey,
3591
- ...(rawExcerptNamespace ? { rawExcerptNamespace } : {}),
3592
- ...(rawExcerptSessionIds !== undefined ? { rawExcerptSessionIds } : {}),
3593
- ...(rawExcerptsSuppressed ? { rawExcerptsSuppressed } : {}),
3594
- });
3595
-
3596
- // Tag filter (issue #689). Applied post-recall, post-serialization so
3597
- // the actual frontmatter tags are already loaded onto each result. When
3598
- // `tags` is absent or empty the filter is a no-op; an invalid `tagMatch`
3599
- // throws via `parseTagMatch` (CLAUDE.md rule 51).
3600
- const filterTags = normalizeTags(request.tags);
3601
- let tagMatchMode: TagMatchMode | undefined;
3602
- try {
3603
- tagMatchMode = parseTagMatch(request.tagMatch);
3604
- } catch (err) {
3605
- throw new EngramAccessInputError(
3606
- err instanceof Error ? err.message : String(err),
3607
- );
3608
- }
3609
- let effectiveContext = context;
3610
- if (filterTags && filterTags.length > 0) {
3611
- const beforeIds = results.map((r) => r.id);
3612
- const { results: admitted } = applyTagFilter(results, {
3613
- tags: filterTags,
3614
- tagMatch: tagMatchMode,
3615
- });
3616
- results = admitted;
3617
- // Codex P1: `context` was generated by orchestrator.recall(...)
3618
- // BEFORE the tag filter ran, so it can contain memories that don't
3619
- // match the requested tags. Surfaces consuming `context` (the
3620
- // prompt-injection string) would leak excluded content into the
3621
- // LLM. When the filter actually drops any result, rebuild context
3622
- // from the admitted set so excluded content is unreachable through
3623
- // any field of the response. The rebuilt context concatenates each
3624
- // admitted result's available text (full content at section/raw
3625
- // disclosure, otherwise the preview) — a different wire format
3626
- // than the orchestrator's native context, but a strict subset
3627
- // safe to inject.
3628
- const admittedIds = new Set(results.map((r) => r.id));
3629
- const droppedAny = beforeIds.some((id) => !admittedIds.has(id));
3630
- if (droppedAny) {
3631
- effectiveContext = results
3632
- .map((r) => {
3633
- const content =
3634
- typeof (r as { content?: unknown }).content === "string"
3635
- ? ((r as { content?: string }).content ?? "")
3636
- : "";
3637
- const preview =
3638
- typeof (r as { preview?: unknown }).preview === "string"
3639
- ? ((r as { preview?: string }).preview ?? "")
3640
- : "";
3641
- return content || preview;
3642
- })
3643
- .filter((s) => s.length > 0)
3644
- .join("\n\n");
3645
- }
3646
- }
3647
- const filteredMemoryIds = filterTags && filterTags.length > 0
3648
- ? results.map((r) => r.id)
3649
- : (snapshot?.memoryIds ?? []);
3650
- const debug = await this.buildRecallDebug(
3651
- snapshot,
3652
- effectiveNamespace,
3653
- request.includeDebug === true,
3654
- request.sessionKey,
2957
+ return this.accessRecallSurface.executeRecall(
2958
+ request,
3655
2959
  );
3656
-
3657
- // Fire-and-forget audit recording. Must never block or crash recall.
3658
- let auditAnomalies: AccessAuditResult["anomalies"] | undefined;
3659
- if (this.auditAdapter) {
3660
- try {
3661
- const resolvedAgentId = principal ?? "__anonymous__";
3662
- const auditEntry = {
3663
- ts: new Date().toISOString(),
3664
- sessionKey: request.sessionKey ?? "",
3665
- agentId: resolvedAgentId,
3666
- trigger: "access-surface",
3667
- queryText: query,
3668
- candidateMemoryIds: snapshot?.memoryIds ?? [],
3669
- // Audit must reflect what was actually injected, not what
3670
- // recall produced before the tag filter. Using `context`
3671
- // (pre-filter) overstates injectedChars and can leak content
3672
- // from excluded memories into the audit summary (cursor
3673
- // Medium on PR #712).
3674
- summary: effectiveContext.slice(0, 200) || null,
3675
- injectedChars: effectiveContext.length,
3676
- toggleState: "enabled" as const,
3677
- latencyMs: Date.now() - startedAt,
3678
- plannerMode: snapshot?.plannerMode ?? mode,
3679
- requestedMode: mode,
3680
- fallbackUsed: snapshot?.fallbackUsed ?? false,
3681
- };
3682
- const auditResult = await this.auditAdapter.record(
3683
- resolvedAgentId || "__anonymous__",
3684
- auditEntry,
3685
- );
3686
- auditAnomalies = auditResult.anomalies;
3687
- } catch {
3688
- // Audit failures must never crash the recall path.
3689
- }
3690
- }
3691
-
3692
- return {
3693
- response: {
3694
- query,
3695
- sessionKey: request.sessionKey,
3696
- namespace: effectiveNamespace,
3697
- context: effectiveContext,
3698
- count: filterTags && filterTags.length > 0
3699
- ? results.length
3700
- : (snapshot?.memoryIds.length ?? results.length),
3701
- memoryIds: filteredMemoryIds,
3702
- results,
3703
- recordedAt: snapshot?.recordedAt,
3704
- traceId: snapshot?.traceId,
3705
- plannerMode: snapshot?.plannerMode ?? mode,
3706
- fallbackUsed: snapshot?.fallbackUsed ?? false,
3707
- sourcesUsed: snapshot?.sourcesUsed ?? [],
3708
- disclosure,
3709
- budgetsApplied: snapshot?.budgetsApplied,
3710
- auditAnomalies,
3711
- budgetWarning: budgetDecision.reason === "warn-over-soft" ? budgetDecision : undefined,
3712
- latencyMs: snapshot?.latencyMs ?? (Date.now() - startedAt),
3713
- debug,
3714
- },
3715
- budgetRecordPrincipal: recordBudgetAfterSuccess ? principal : null,
3716
- };
3717
2960
  }
3718
2961
 
3719
2962
  async recallExplain(
@@ -3814,15 +3057,6 @@ export class EngramAccessService {
3814
3057
  return toRecallExplainJson(snapshot);
3815
3058
  }
3816
3059
 
3817
- /**
3818
- * Recall X-ray (issue #570). Runs a recall with `xrayCapture: true`
3819
- * and returns the resulting snapshot as structured JSON so every
3820
- * surface (CLI / HTTP / MCP) gets the same payload. Namespace scope
3821
- * is enforced before the recall fires (CLAUDE.md rule 42 — read and
3822
- * write paths must resolve through the same namespace layer) so an
3823
- * unauthorized principal cannot capture an x-ray for a namespace it
3824
- * cannot read.
3825
- */
3826
3060
  async recallXray(request: {
3827
3061
  query: string;
3828
3062
  sessionKey?: string;
@@ -3867,433 +3101,9 @@ export class EngramAccessService {
3867
3101
  snapshot?: RecallXraySnapshot;
3868
3102
  recall?: EngramAccessRecallResponse;
3869
3103
  }> {
3870
- const query = typeof request.query === "string" ? request.query : "";
3871
- if (query.trim().length === 0) {
3872
- // Match the CLI contract (CLAUDE.md rule 51): reject empty
3873
- // input with an explicit error rather than silently producing
3874
- // an empty snapshot.
3875
- throw new Error("recallXray: query is required and must be non-empty");
3876
- }
3877
- // Validate disclosure UP FRONT — before recall executes, before
3878
- // the xray queue mutex is acquired, before namespace resolution.
3879
- // A bad value should fail fast rather than after we've burned
3880
- // cycles on an irreversible recall (Cursor Medium review on PR
3881
- // #699).
3882
- if (
3883
- request.disclosure !== undefined &&
3884
- !isRecallDisclosure(request.disclosure)
3885
- ) {
3886
- throw new EngramAccessInputError(
3887
- `recallXray: disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`,
3888
- );
3889
- }
3890
-
3891
- const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
3892
- const requestedNamespace = request.namespace?.trim()
3893
- ? this.resolveNamespace(request.namespace)
3894
- : undefined;
3895
- const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
3896
- const principal =
3897
- authenticatedPrincipal
3898
- || resolvePrincipal(request.sessionKey, this.orchestrator.config);
3899
-
3900
- if (requestedNamespace) {
3901
- if (
3902
- !canReadNamespace(
3903
- principal,
3904
- requestedNamespace,
3905
- this.orchestrator.config,
3906
- )
3907
- ) {
3908
- return { snapshotFound: false };
3909
- }
3910
- } else if (
3911
- namespacesEnabled
3912
- && !authenticatedPrincipal
3913
- && !request.sessionKey?.trim()
3914
- ) {
3915
- // Namespaces enabled but no identity supplied — reject rather
3916
- // than scanning the global namespace (CLAUDE.md rule 48:
3917
- // least-privileged default).
3918
- return { snapshotFound: false };
3919
- }
3920
-
3921
- // Optional `--budget` override must be a positive integer. Invalid
3922
- // values throw rather than silently defaulting (CLAUDE.md rule 51).
3923
- let budgetOverride: number | undefined;
3924
- if (request.budget !== undefined && request.budget !== null) {
3925
- const parsed =
3926
- typeof request.budget === "number"
3927
- ? request.budget
3928
- : Number(request.budget);
3929
- if (
3930
- !Number.isFinite(parsed)
3931
- || parsed <= 0
3932
- || !Number.isInteger(parsed)
3933
- ) {
3934
- throw new Error(
3935
- `recallXray: budget expects a positive integer; got ${JSON.stringify(request.budget)}`,
3936
- );
3937
- }
3938
- budgetOverride = parsed;
3939
- }
3940
- const mode = this.normalizeRecallMode(request.mode);
3941
- const disclosure = request.disclosure ?? DEFAULT_RECALL_DISCLOSURE;
3942
-
3943
- // Serialize x-ray invocations behind a per-service mutex so the
3944
- // per-process `getLastXraySnapshot()` slot cannot be clobbered by
3945
- // a concurrent capturing call before this caller reads it back.
3946
- // Budget and principal are now threaded through
3947
- // `RecallInvocationOptions`, so global config mutation is gone
3948
- // (CLAUDE.md rule 47: no shared mutable state across async
3949
- // boundaries). The mutex stays only for the snapshot-slot
3950
- // ordering guarantee.
3951
- const previousQueue = this.xrayQueue;
3952
- let release: () => void = () => {};
3953
- this.xrayQueue = new Promise<void>((resolve) => {
3954
- release = resolve;
3955
- });
3956
- await previousQueue;
3957
- const recallStartedAt = Date.now();
3958
-
3959
- const recallSessionKey = request.sessionKey?.trim() || undefined;
3960
- let xrayResponse: {
3961
- snapshotFound: boolean;
3962
- snapshot?: RecallXraySnapshot;
3963
- } = { snapshotFound: false };
3964
-
3965
- try {
3966
- // Clear any prior snapshot so a capture failure surfaces as
3967
- // `{snapshotFound: false}` rather than returning stale data
3968
- // from an earlier call on the same orchestrator.
3969
- this.orchestrator.clearLastXraySnapshot();
3970
- await this.orchestrator.recall(query, recallSessionKey, {
3971
- xrayCapture: true,
3972
- ...(requestedNamespace ? { namespace: requestedNamespace } : {}),
3973
- ...(budgetOverride !== undefined
3974
- ? { budgetCharsOverride: budgetOverride }
3975
- : {}),
3976
- ...(mode !== undefined ? { mode } : {}),
3977
- // When the caller supplies an authenticated principal, forward
3978
- // it via the dedicated override channel so orchestrator-side
3979
- // ACL decisions use the SAME principal the access-surface
3980
- // pre-check above authorized. Threading an
3981
- // `authenticatedPrincipal` through `sessionKey` would be wrong:
3982
- // `resolvePrincipal(sessionKey)` only maps configured raw
3983
- // session keys and otherwise collapses to `"default"`, which
3984
- // in namespace-enabled deployments produces false denials /
3985
- // wrong-scope serving despite the pre-check passing
3986
- // (CLAUDE.md rule 42).
3987
- ...(authenticatedPrincipal
3988
- ? { principalOverride: authenticatedPrincipal }
3989
- : {}),
3990
- ...(request.currentContextScopes !== undefined
3991
- ? { currentContextScopes: request.currentContextScopes }
3992
- : {}),
3993
- });
3994
-
3995
- const rawSnapshot = this.orchestrator.getLastXraySnapshot();
3996
- // Re-check namespace after capture: the recall may have served
3997
- // from a different namespace than the caller requested. Drop
3998
- // the snapshot rather than leak cross-tenant data (CLAUDE.md
3999
- // rules 42 + 47). The comparison is strict so a snapshot whose
4000
- // namespace is `undefined` cannot bypass the scope the caller
4001
- // asked for.
4002
- const namespaceMismatch =
4003
- requestedNamespace !== undefined &&
4004
- rawSnapshot?.namespace !== requestedNamespace;
4005
- if (!rawSnapshot) {
4006
- xrayResponse = { snapshotFound: false };
4007
- } else if (namespaceMismatch) {
4008
- xrayResponse = { snapshotFound: false };
4009
- } else {
4010
- // Tag filter (issue #689). Mirrors `recall()` semantics — applied
4011
- // post-capture by reading each result's frontmatter tags and
4012
- // dropping non-matching results. Filter activity surfaces as a
4013
- // `tag-filter` entry in `snapshot.filters` so X-ray consumers can
4014
- // see the "considered → admitted" delta.
4015
- let snapshot = rawSnapshot;
4016
- const xrayFilterTags = normalizeTags(request.tags);
4017
- let xrayTagMatch: TagMatchMode | undefined;
4018
- try {
4019
- xrayTagMatch = parseTagMatch(request.tagMatch);
4020
- } catch (err) {
4021
- throw new EngramAccessInputError(
4022
- err instanceof Error ? err.message : String(err),
4023
- );
4024
- }
4025
- if (xrayFilterTags && xrayFilterTags.length > 0) {
4026
- const namespace = snapshot.namespace
4027
- ? this.resolveNamespace(snapshot.namespace)
4028
- : this.orchestrator.config.defaultNamespace;
4029
- const tagsByIndex = await Promise.all(
4030
- snapshot.results.map(async (result) => {
4031
- try {
4032
- const storage = await this.orchestrator.getStorage(namespace);
4033
- const memory = await storage.readMemoryByPath(result.path);
4034
- const t = memory?.frontmatter?.tags;
4035
- // Normalize identically to the recall path
4036
- // (`normalizeProjectionTags`): trim and drop empty strings
4037
- // so X-ray tag matching stays consistent with the recall
4038
- // surface. Without this, a frontmatter tag like " draft "
4039
- // would match in recall but not in X-ray (cursor review).
4040
- return Array.isArray(t) ? normalizeProjectionTags(t) : [];
4041
- } catch {
4042
- return [];
4043
- }
4044
- }),
4045
- );
4046
- const tagged = snapshot.results.map((result, index) => ({
4047
- result,
4048
- tags: tagsByIndex[index] ?? [],
4049
- }));
4050
- const { results: admittedTagged, trace } = applyTagFilter(tagged, {
4051
- tags: xrayFilterTags,
4052
- tagMatch: xrayTagMatch,
4053
- });
4054
- const admittedResults = admittedTagged.map((entry) => entry.result);
4055
- const filters = trace ? [...snapshot.filters, trace] : snapshot.filters;
4056
- snapshot = { ...snapshot, results: admittedResults, filters };
4057
- }
4058
- // Decorate per-result disclosure + token estimate when the caller
4059
- // wired a depth knob (issue #677 PR 3/4 — codex review on #699
4060
- // flagged that the renderer's per-disclosure summary stays empty
4061
- // until callers populate these fields). Estimate tokens from
4062
- // the actual rendered payload at the requested depth so the
4063
- // summary reflects real spend; chunk uses the preview, section
4064
- // and raw use full content. Best-effort only — a missing
4065
- // memory or read failure is silently dropped (CLAUDE.md rule 13).
4066
- if (request.disclosure !== undefined) {
4067
- // Disclosure already validated up front; pin to the narrowed
4068
- // type here. Re-validation inside the queue would be dead code.
4069
- const disclosure: RecallDisclosure = request.disclosure;
4070
- const namespace = snapshot.namespace
4071
- ? this.resolveNamespace(snapshot.namespace)
4072
- : this.orchestrator.config.defaultNamespace;
4073
- // Pre-fetch raw excerpts ONCE so the first raw-disclosure
4074
- // result's token estimate includes the LCM-side excerpt spend
4075
- // that `shapeMemorySummary` actually attaches in the recall
4076
- // response. Without this, raw recalls systematically
4077
- // undercounted spend on the first result (Cursor Medium review
4078
- // on PR #699). Excerpts are scoped to the same session +
4079
- // namespace as the recall.
4080
- // Trim sessionKey to match what `orchestrator.recall(...)`
4081
- // already does (`request.sessionKey?.trim() || undefined`),
4082
- // otherwise a whitespace-padded key drives recall under one
4083
- // identity but probes LCM under a different prefix and
4084
- // misses stored excerpts (Cursor Low review on PR #699).
4085
- const trimmedSessionKey = request.sessionKey?.trim() || undefined;
4086
- // Read-authorization-gated namespace for the raw-excerpt LCM lookup
4087
- // (#1505 thread 2f7). NOT `snapshot.namespace` (the write/overlay
4088
- // namespace), which would attach `<principal>-project-*` overlay rows
4089
- // when the principal can WRITE but not READ its self base. Mirrors the
4090
- // recall + `lcmSearch` read gate. Resolved ONLY for raw disclosure (its
4091
- // sole consumer); the `namespace` above is still used for the
4092
- // memory-FILE reads below (a separate, snapshot-scoped read), so non-raw
4093
- // x-ray decoration stays byte-for-byte unchanged.
4094
- const rawExcerptNamespace =
4095
- disclosure === "raw"
4096
- ? this.resolveRawExcerptReadNamespace(
4097
- request.namespace,
4098
- trimmedSessionKey,
4099
- authenticatedPrincipal,
4100
- )
4101
- : namespace;
4102
- // `undefined` for an IMPLICIT raw recall means NO readable LCM namespace
4103
- // exists (restrictive `default` READ policy, no readable overlay/self)
4104
- // — suppress excerpts rather than fall back to the write/overlay
4105
- // namespace the read gate excludes (#1505 thread NBHWz).
4106
- const xrayHasExplicitNamespace =
4107
- typeof request.namespace === "string" &&
4108
- request.namespace.trim().length > 0;
4109
- const rawExcerptsSuppressed =
4110
- disclosure === "raw" &&
4111
- !xrayHasExplicitNamespace &&
4112
- rawExcerptNamespace === undefined;
4113
- // Ordered, read-authorized LCM read key SET (#1505 fallback
4114
- // unification) so raw disclosure finds excerpts a branch-scoped session
4115
- // archived at project/root scope — exactly as recall + `lcmSearch` do.
4116
- // Only meaningful with a concrete sessionKey + a readable namespace;
4117
- // already read-gated so no unauthorized overlay key is present.
4118
- const rawExcerptSessionIds =
4119
- disclosure === "raw" && trimmedSessionKey && rawExcerptNamespace
4120
- ? this.resolveLcmReadSessionIds(
4121
- request.namespace,
4122
- rawExcerptNamespace,
4123
- trimmedSessionKey,
4124
- authenticatedPrincipal,
4125
- )
4126
- : undefined;
4127
- const rawExcerpts =
4128
- disclosure === "raw" && !rawExcerptsSuppressed
4129
- ? await this.fetchRawExcerpts(disclosure, {
4130
- query,
4131
- ...(trimmedSessionKey ? { sessionKey: trimmedSessionKey } : {}),
4132
- ...(rawExcerptNamespace
4133
- ? { namespace: rawExcerptNamespace }
4134
- : {}),
4135
- ...(rawExcerptSessionIds !== undefined
4136
- ? { lcmSessionIds: rawExcerptSessionIds }
4137
- : {}),
4138
- })
4139
- : disclosure === "raw"
4140
- ? []
4141
- : null;
4142
- const rawExcerptText =
4143
- rawExcerpts && rawExcerpts.length > 0
4144
- ? rawExcerpts.map((e) => e.content).join("\n")
4145
- : "";
4146
- // Pre-load every memory in parallel so we can:
4147
- // (a) re-attribute raw excerpts to the *first readable* result
4148
- // rather than always to index 0 (Cursor Low review on PR
4149
- // #699: a missing/unreadable result[0] orphaned the excerpt
4150
- // budget); and
4151
- // (b) include the metadata fields `shapeMemorySummary` actually
4152
- // emits at every depth (id, path, category, status, created,
4153
- // updated, tags, entityRef) in the token estimate, so the
4154
- // summary reflects real spend rather than only payload-body
4155
- // spend (Cursor Low review on PR #699).
4156
- const memoryByIndex = await Promise.all(
4157
- snapshot.results.map(async (result) => {
4158
- try {
4159
- const storage = await this.orchestrator.getStorage(namespace);
4160
- return await storage.readMemoryByPath(result.path);
4161
- } catch {
4162
- return null;
4163
- }
4164
- }),
4165
- );
4166
- const firstReadableIndex = memoryByIndex.findIndex((m) => m !== null);
4167
- const baseDir =
4168
- (await this.orchestrator.getStorage(namespace)).dir;
4169
- const decorated = snapshot.results.map((result, index) => {
4170
- const memory = memoryByIndex[index];
4171
- if (!memory) {
4172
- // Unreadable result: attach the disclosure tag anyway so
4173
- // the per-disclosure summary classifies it correctly,
4174
- // but skip the token estimate since we don't have the
4175
- // content to measure. Without the disclosure tag the
4176
- // result silently flows into the `unspecified` bucket
4177
- // even though the caller explicitly requested a depth
4178
- // (Cursor Low review on PR #699).
4179
- return { ...result, disclosure };
4180
- }
4181
- // Build a representative shaped summary so the estimate
4182
- // counts every field `shapeMemorySummary` actually emits.
4183
- // The serialized JSON form is a close-enough proxy for the
4184
- // wire payload size.
4185
- const shaped = shapeMemorySummary(
4186
- memory,
4187
- baseDir,
4188
- disclosure,
4189
- disclosure === "raw" &&
4190
- index === firstReadableIndex &&
4191
- rawExcerpts &&
4192
- rawExcerpts.length > 0
4193
- ? rawExcerpts
4194
- : undefined,
4195
- );
4196
- return {
4197
- ...result,
4198
- disclosure,
4199
- estimatedTokens: estimateRecallTokens(JSON.stringify(shaped)),
4200
- };
4201
- });
4202
- // Edge case: every result was unreadable but rawExcerpts
4203
- // still has content — credit that spend to result[0] rather
4204
- // than dropping it on the floor. Without this, the raw row
4205
- // in the per-disclosure summary under-reports spend whenever
4206
- // every memory file is missing/unreadable.
4207
- if (
4208
- disclosure === "raw" &&
4209
- firstReadableIndex === -1 &&
4210
- rawExcerptText.length > 0 &&
4211
- decorated.length > 0
4212
- ) {
4213
- decorated[0] = {
4214
- ...decorated[0]!,
4215
- disclosure,
4216
- estimatedTokens: estimateRecallTokens(rawExcerptText),
4217
- };
4218
- }
4219
- const decoratedSnapshot = { ...snapshot, results: decorated };
4220
- xrayResponse = {
4221
- snapshotFound: true,
4222
- snapshot: decoratedSnapshot,
4223
- };
4224
- } else {
4225
- xrayResponse = {
4226
- snapshotFound: true,
4227
- snapshot,
4228
- };
4229
- }
4230
- }
4231
- } finally {
4232
- release();
4233
- }
4234
-
4235
- if (
4236
- request.includeRecall === true &&
4237
- xrayResponse.snapshotFound === true &&
4238
- xrayResponse.snapshot
4239
- ) {
4240
- // Same read-authorization-gated raw-excerpt namespace the recall path uses
4241
- // (#1505 thread 2f7), so the includeRecall x-ray path can't leak overlay
4242
- // transcript rows via raw disclosure. Resolved ONLY for raw disclosure (the
4243
- // sole consumer) so non-raw x-ray recall stays byte-for-byte unchanged. The
4244
- // ordered LCM read key SET (#1505 fallback unification) adds the coding read
4245
- // fallbacks so a branch-scoped session also finds excerpts at project/root
4246
- // scope.
4247
- const xrayRawExcerptNamespace =
4248
- disclosure === "raw"
4249
- ? this.resolveRawExcerptReadNamespace(
4250
- request.namespace,
4251
- recallSessionKey,
4252
- authenticatedPrincipal,
4253
- )
4254
- : undefined;
4255
- // `undefined` for an IMPLICIT raw recall means NO readable LCM namespace
4256
- // exists — suppress excerpts rather than fall back to the write/overlay
4257
- // namespace the read gate excludes (#1505 thread NBHWz).
4258
- const xrayHasExplicitNamespace =
4259
- typeof request.namespace === "string" &&
4260
- request.namespace.trim().length > 0;
4261
- const xrayRawExcerptsSuppressed =
4262
- disclosure === "raw" &&
4263
- !xrayHasExplicitNamespace &&
4264
- xrayRawExcerptNamespace === undefined;
4265
- const xrayRawExcerptSessionIds =
4266
- disclosure === "raw" && xrayRawExcerptNamespace && recallSessionKey
4267
- ? this.resolveLcmReadSessionIds(
4268
- request.namespace,
4269
- xrayRawExcerptNamespace,
4270
- recallSessionKey,
4271
- authenticatedPrincipal,
4272
- )
4273
- : undefined;
4274
- return {
4275
- ...xrayResponse,
4276
- recall: await this.buildRecallResponseFromXraySnapshot({
4277
- query,
4278
- sessionKey: recallSessionKey,
4279
- snapshot: xrayResponse.snapshot,
4280
- disclosure,
4281
- startedAt: recallStartedAt,
4282
- requestedMode: request.mode,
4283
- normalizedMode: mode,
4284
- ...(xrayRawExcerptNamespace
4285
- ? { rawExcerptNamespace: xrayRawExcerptNamespace }
4286
- : {}),
4287
- ...(xrayRawExcerptSessionIds !== undefined
4288
- ? { rawExcerptSessionIds: xrayRawExcerptSessionIds }
4289
- : {}),
4290
- ...(xrayRawExcerptsSuppressed
4291
- ? { rawExcerptsSuppressed: xrayRawExcerptsSuppressed }
4292
- : {}),
4293
- }),
4294
- };
4295
- }
4296
- return xrayResponse;
3104
+ return this.accessRecallSurface.recallXray(
3105
+ request,
3106
+ );
4297
3107
  }
4298
3108
  // Sequence lock for `recallXray` — see comment inside the method.
4299
3109
  // Lives on the instance so every x-ray call on the same service