@remnic/core 9.64.1 → 9.64.2

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 (462) hide show
  1. package/dist/access-admin-ops-surface.d.ts +12 -11
  2. package/dist/access-admin-ops-surface.js +23 -22
  3. package/dist/access-authorization-probe.d.ts +12 -11
  4. package/dist/access-authorization-probe.js +24 -23
  5. package/dist/access-boundary.d.ts +13 -12
  6. package/dist/access-boundary.js +23 -22
  7. package/dist/access-cli.js +70 -68
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +12 -11
  11. package/dist/access-extraction-force-flush.js +23 -22
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +12 -11
  14. package/dist/access-http-lifecycle-flush.d.ts +12 -11
  15. package/dist/access-http-offline-stream.d.ts +12 -11
  16. package/dist/access-http-query.d.ts +2 -2
  17. package/dist/access-http-query.js +25 -24
  18. package/dist/access-http.d.ts +13 -12
  19. package/dist/access-http.js +40 -39
  20. package/dist/access-identity-continuity-surface.d.ts +9 -9
  21. package/dist/access-identity-continuity-surface.js +23 -22
  22. package/dist/access-lcm-surface.d.ts +12 -11
  23. package/dist/access-lcm-surface.js +23 -22
  24. package/dist/access-mcp-output-schemas.js +1 -1
  25. package/dist/access-mcp.d.ts +12 -186
  26. package/dist/access-mcp.js +32 -31
  27. package/dist/access-memory-search-fanout.d.ts +2 -2
  28. package/dist/{access-namespace-preflight-Dj9uR4Cq.d.ts → access-namespace-preflight-CYeI1R2_.d.ts} +1 -1
  29. package/dist/access-namespace-preflight.d.ts +3 -3
  30. package/dist/access-namespace-preflight.js +23 -22
  31. package/dist/access-observe-write-surface.d.ts +12 -11
  32. package/dist/access-observe-write-surface.js +23 -22
  33. package/dist/access-offline-manifest.d.ts +9 -9
  34. package/dist/access-operations-batch.js +26 -25
  35. package/dist/access-operations.d.ts +16 -15
  36. package/dist/access-operations.js +29 -28
  37. package/dist/access-recall-concurrency.d.ts +12 -11
  38. package/dist/access-recall-concurrency.js +23 -22
  39. package/dist/access-recall-response.d.ts +12 -11
  40. package/dist/access-recall-response.js +23 -22
  41. package/dist/access-recall-surface.d.ts +12 -11
  42. package/dist/access-recall-surface.js +23 -22
  43. package/dist/access-schema.d.ts +88 -88
  44. package/dist/access-schema.js +3 -3
  45. package/dist/{access-service-Bd0lzaHp.d.ts → access-service-BKtdXnyk.d.ts} +358 -76
  46. package/dist/access-service-helpers.d.ts +12 -11
  47. package/dist/access-service.d.ts +12 -11
  48. package/dist/access-service.js +23 -22
  49. package/dist/access-surface-catalog.d.ts +12 -11
  50. package/dist/access-surface-catalog.js +27 -24
  51. package/dist/access-surface-catalog.js.map +1 -1
  52. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  53. package/dist/action-confidence.d.ts +1 -1
  54. package/dist/active-memory-bridge.d.ts +1 -1
  55. package/dist/active-recall.d.ts +1 -1
  56. package/dist/active-recall.js +1 -1
  57. package/dist/adapters/index.js +4 -4
  58. package/dist/adapters/registry.js +2 -2
  59. package/dist/ambient-provenance.d.ts +1 -1
  60. package/dist/artifact-search.d.ts +1 -1
  61. package/dist/behavior-learner.d.ts +1 -1
  62. package/dist/behavior-signals.d.ts +1 -1
  63. package/dist/bootstrap.d.ts +9 -9
  64. package/dist/briefing-window.d.ts +2 -2
  65. package/dist/briefing.d.ts +2 -2
  66. package/dist/briefing.js +10 -10
  67. package/dist/buffer-surprise-report.d.ts +1 -1
  68. package/dist/buffer-turn-helpers.d.ts +1 -1
  69. package/dist/buffer.d.ts +2 -2
  70. package/dist/bulk-import/index.d.ts +3 -3
  71. package/dist/calibration.d.ts +1 -1
  72. package/dist/capabilities.d.ts +1 -1
  73. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  74. package/dist/{catalog-rKYbN7zF.d.ts → catalog-CLAqQO2L.d.ts} +1 -1
  75. package/dist/causal-behavior.d.ts +1 -1
  76. package/dist/causal-consolidation.d.ts +1 -1
  77. package/dist/causal-consolidation.js +11 -11
  78. package/dist/causal-trajectory-graph.d.ts +1 -1
  79. package/dist/{chunk-D267K4V5.js → chunk-246HFJVG.js} +2 -2
  80. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  81. package/dist/{chunk-2RZCOG63.js → chunk-2QI2JBLQ.js} +359 -238
  82. package/dist/chunk-2QI2JBLQ.js.map +1 -0
  83. package/dist/{chunk-WNO5W2TP.js → chunk-3A46U7XG.js} +2 -2
  84. package/dist/{chunk-XFAWGUNX.js → chunk-3LOQN3TO.js} +10 -5
  85. package/dist/chunk-3LOQN3TO.js.map +1 -0
  86. package/dist/{chunk-5Q5BEWJ3.js → chunk-44DSGZFF.js} +2 -2
  87. package/dist/{chunk-VENMDW4U.js → chunk-55SJFHVD.js} +6 -6
  88. package/dist/{chunk-VCBG52MM.js → chunk-5HERQ5FX.js} +7 -7
  89. package/dist/{chunk-IRXMJLTW.js → chunk-5HK3CJAR.js} +2 -2
  90. package/dist/{chunk-VSKA2ATE.js → chunk-6LTCUN3T.js} +2 -2
  91. package/dist/{chunk-IHPYTG6F.js → chunk-7BVO64UC.js} +2 -2
  92. package/dist/{chunk-ZUIVJ3BB.js → chunk-7V3AZWWZ.js} +4 -4
  93. package/dist/{chunk-Y5ZIQFWH.js → chunk-A2IBK3TG.js} +2 -2
  94. package/dist/{chunk-6I6IOMKS.js → chunk-C6AKYOFO.js} +2 -2
  95. package/dist/{chunk-ZGGTIBWN.js → chunk-CKZHKFY3.js} +3 -3
  96. package/dist/{chunk-FMGTFMT3.js → chunk-DA2O3F5V.js} +25 -11
  97. package/dist/chunk-DA2O3F5V.js.map +1 -0
  98. package/dist/{chunk-GSQDPL45.js → chunk-DC4FJY2G.js} +22 -7
  99. package/dist/{chunk-GSQDPL45.js.map → chunk-DC4FJY2G.js.map} +1 -1
  100. package/dist/{chunk-JE3AKPOO.js → chunk-DG6MJD7W.js} +2 -2
  101. package/dist/{chunk-P4SD7BSA.js → chunk-DHKV6E4A.js} +2 -2
  102. package/dist/{chunk-KOE3RLNN.js → chunk-EJDLQRUO.js} +2 -2
  103. package/dist/{chunk-VD2OT77P.js → chunk-GEULB2VI.js} +2 -1
  104. package/dist/chunk-GEULB2VI.js.map +1 -0
  105. package/dist/{chunk-DRS3OX77.js → chunk-GVRLED7R.js} +2 -2
  106. package/dist/{chunk-EAWJSY2Z.js → chunk-H5CGAD7L.js} +1 -1
  107. package/dist/{chunk-DLZBOEJY.js → chunk-IFK3PVM7.js} +2 -2
  108. package/dist/{chunk-E7MYSCSL.js → chunk-IJXECCUV.js} +2 -2
  109. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  110. package/dist/{chunk-NMQXUGTU.js → chunk-JCSUNXYJ.js} +3 -3
  111. package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
  112. package/dist/{chunk-YY63AMGP.js → chunk-KRPE7ANR.js} +2 -2
  113. package/dist/{chunk-AGC3Q53R.js → chunk-LOETWOEB.js} +2 -2
  114. package/dist/{chunk-7X35RPN3.js → chunk-LZKCMK3U.js} +2 -2
  115. package/dist/{chunk-4KKVDOCD.js → chunk-MJRVF7SF.js} +26 -25
  116. package/dist/chunk-MJRVF7SF.js.map +1 -0
  117. package/dist/{chunk-K547KS3X.js → chunk-ML3U4KPR.js} +4 -4
  118. package/dist/{chunk-HIEWDJ2O.js → chunk-MSTNSVXI.js} +2 -1
  119. package/dist/chunk-MSTNSVXI.js.map +1 -0
  120. package/dist/{chunk-CI6YIT5S.js → chunk-NO7B6DRB.js} +2 -2
  121. package/dist/{chunk-Q3IRE6E4.js → chunk-ON2P3SHG.js} +2 -2
  122. package/dist/{chunk-2Q4UAPLD.js → chunk-OQIRBGUU.js} +4 -4
  123. package/dist/{chunk-WDFXRHOZ.js → chunk-PE76XIDM.js} +3 -3
  124. package/dist/{chunk-MVIYRRVJ.js → chunk-PMVPCZJZ.js} +5 -5
  125. package/dist/{chunk-VCNGVU62.js → chunk-PVIXAMWM.js} +2 -2
  126. package/dist/{chunk-QHNO6AND.js → chunk-RWLRKMZK.js} +2 -2
  127. package/dist/{chunk-UWSETW6O.js → chunk-SB6YBB2V.js} +2 -2
  128. package/dist/{chunk-DTQ3OB7O.js → chunk-SHQFYZTD.js} +2 -2
  129. package/dist/chunk-TKI2WOL5.js +256 -0
  130. package/dist/chunk-TKI2WOL5.js.map +1 -0
  131. package/dist/chunk-TY35YQ6E.js +77 -0
  132. package/dist/chunk-TY35YQ6E.js.map +1 -0
  133. package/dist/{chunk-AL43367I.js → chunk-UMDZXW5J.js} +23 -1
  134. package/dist/chunk-UMDZXW5J.js.map +1 -0
  135. package/dist/{chunk-G5OB4XZX.js → chunk-VANZKHTN.js} +4 -4
  136. package/dist/{chunk-IPUUMLSM.js → chunk-VLES5S4K.js} +271 -62
  137. package/dist/chunk-VLES5S4K.js.map +1 -0
  138. package/dist/{chunk-DPPTG6VJ.js → chunk-VRYEXPRX.js} +1 -1
  139. package/dist/chunk-VRYEXPRX.js.map +1 -0
  140. package/dist/{chunk-3CYVTO6A.js → chunk-VUP47UJI.js} +3 -3
  141. package/dist/{chunk-OTVHRZZD.js → chunk-WBM5NR4E.js} +2 -2
  142. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  143. package/dist/{chunk-7D3TTGHY.js → chunk-XACYILRA.js} +38 -12
  144. package/dist/chunk-XACYILRA.js.map +1 -0
  145. package/dist/{chunk-B2GICNOJ.js → chunk-XH4IQVTN.js} +2 -2
  146. package/dist/{chunk-OWAYQK5V.js → chunk-XZUCNVGE.js} +8 -8
  147. package/dist/{chunk-4FSCGYTQ.js → chunk-YFQTDE7N.js} +2 -2
  148. package/dist/{chunk-RMB22Q3R.js → chunk-YXGU3MDQ.js} +2 -2
  149. package/dist/{chunk-XSPRZZEX.js → chunk-Z6CJLYXT.js} +4 -4
  150. package/dist/{chunk-DZ2AVS2H.js → chunk-ZSYE2QVJ.js} +2 -2
  151. package/dist/{cli-0dDmOsJ8.d.ts → cli-CLvBewKf.d.ts} +5 -5
  152. package/dist/cli.d.ts +13 -13
  153. package/dist/cli.js +56 -55
  154. package/dist/coding/pre-action-gate.d.ts +1 -1
  155. package/dist/compounding/engine.d.ts +2 -2
  156. package/dist/compounding/engine.js +10 -10
  157. package/dist/compounding/preference-consolidator.d.ts +1 -1
  158. package/dist/compression-optimizer.d.ts +1 -1
  159. package/dist/config.d.ts +1 -1
  160. package/dist/config.js +1 -1
  161. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  162. package/dist/connectors/codex-materialize-runner.js +10 -10
  163. package/dist/connectors/codex-materialize.d.ts +1 -1
  164. package/dist/connectors/index.d.ts +1 -1
  165. package/dist/connectors/index.js +10 -10
  166. package/dist/consolidation-provenance-check.d.ts +2 -2
  167. package/dist/consolidation-undo.d.ts +2 -2
  168. package/dist/contradiction/index.d.ts +2 -2
  169. package/dist/contradiction/index.js +4 -4
  170. package/dist/converge-config.d.ts +1 -1
  171. package/dist/convergence-refresh.d.ts +2 -2
  172. package/dist/convergence-refresh.js +12 -12
  173. package/dist/conversation-index/backend.d.ts +1 -1
  174. package/dist/conversation-index/chunker.d.ts +1 -1
  175. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  176. package/dist/conversation-index/indexer.d.ts +1 -1
  177. package/dist/conversation-index/search.d.ts +1 -1
  178. package/dist/corpus-watermark.d.ts +1 -1
  179. package/dist/corpus-watermark.js +12 -12
  180. package/dist/day-summary.d.ts +1 -1
  181. package/dist/delinearize.d.ts +1 -1
  182. package/dist/dependency-propagation-config.d.ts +1 -1
  183. package/dist/{dependency-propagation-delivery-Cr8u-XwC.d.ts → dependency-propagation-delivery-DMqIw9Zh.d.ts} +1 -1
  184. package/dist/direct-answer-wiring.d.ts +1 -1
  185. package/dist/direct-answer.d.ts +1 -1
  186. package/dist/embedding-fallback.d.ts +1 -1
  187. package/dist/enrichment/index.d.ts +1 -1
  188. package/dist/entity-origin-fields.d.ts +1 -1
  189. package/dist/entity-retrieval.d.ts +2 -2
  190. package/dist/entity-retrieval.js +10 -10
  191. package/dist/entity-schema.d.ts +1 -1
  192. package/dist/episodic-context.d.ts +1 -1
  193. package/dist/explicit-capture.d.ts +9 -9
  194. package/dist/external-wiki-access.d.ts +12 -11
  195. package/dist/external-wiki-access.js +24 -23
  196. package/dist/external-wiki-collection-registration.d.ts +1 -1
  197. package/dist/external-wiki-collection.d.ts +1 -1
  198. package/dist/external-wiki-mcp-tools.d.ts +12 -13
  199. package/dist/extraction-error-classification.d.ts +1 -1
  200. package/dist/extraction-faithfulness.d.ts +1 -1
  201. package/dist/extraction-judge-telemetry.d.ts +1 -1
  202. package/dist/extraction-judge-training.d.ts +1 -1
  203. package/dist/extraction-judge.d.ts +1 -1
  204. package/dist/extraction-liveness.d.ts +1 -1
  205. package/dist/extraction-normalization.d.ts +1 -1
  206. package/dist/extraction-prompt.d.ts +1 -1
  207. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  208. package/dist/extraction-source-grounding.d.ts +1 -1
  209. package/dist/extraction.d.ts +1 -1
  210. package/dist/extraction.js +10 -10
  211. package/dist/fallback-llm.d.ts +1 -1
  212. package/dist/graph-dashboard-diff.d.ts +1 -1
  213. package/dist/graph-dashboard-key.d.ts +1 -1
  214. package/dist/graph-dashboard-parser.d.ts +1 -1
  215. package/dist/graph-edge-reinforcement.d.ts +1 -1
  216. package/dist/graph-path-reconstruction.d.ts +1 -1
  217. package/dist/graph-path-scoring.d.ts +1 -1
  218. package/dist/graph-snapshot.d.ts +1 -1
  219. package/dist/graph.d.ts +1 -1
  220. package/dist/harmonic-retrieval.js +1 -1
  221. package/dist/importance.d.ts +1 -1
  222. package/dist/importers/index.d.ts +1 -1
  223. package/dist/in-flight-reads.d.ts +1 -1
  224. package/dist/index.d.ts +709 -700
  225. package/dist/index.js +116 -80
  226. package/dist/intent.d.ts +1 -1
  227. package/dist/lcm/engine.d.ts +1 -1
  228. package/dist/lcm/engine.js +3 -3
  229. package/dist/lcm/index.d.ts +1 -1
  230. package/dist/lcm/index.js +11 -11
  231. package/dist/lcm/tools.d.ts +1 -1
  232. package/dist/lifecycle.d.ts +1 -1
  233. package/dist/live-connectors-runner.d.ts +1 -1
  234. package/dist/local-llm.d.ts +1 -1
  235. package/dist/local-model-endpoint.d.ts +1 -1
  236. package/dist/maintenance/memory-governance.d.ts +1 -1
  237. package/dist/maintenance/memory-governance.js +10 -10
  238. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  239. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  240. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  241. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  242. package/dist/{maintenance-BpNjuZk4.d.ts → maintenance-DITiehFX.d.ts} +3 -3
  243. package/dist/mcp-memory-inspector-app.d.ts +12 -11
  244. package/dist/mcp-read-only-tools.js +1 -1
  245. package/dist/memory-action-policy.d.ts +1 -1
  246. package/dist/memory-cache.d.ts +1 -1
  247. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  248. package/dist/memory-projection-store.d.ts +1 -1
  249. package/dist/memory-provenance.d.ts +1 -1
  250. package/dist/memory-snapshot.d.ts +1 -1
  251. package/dist/memory-worth-outcomes.d.ts +2 -2
  252. package/dist/models-json.d.ts +1 -1
  253. package/dist/namespaces/migrate.d.ts +3 -3
  254. package/dist/namespaces/migrate.js +11 -11
  255. package/dist/namespaces/principal.d.ts +1 -1
  256. package/dist/namespaces/search.d.ts +1 -1
  257. package/dist/namespaces/search.js +7 -7
  258. package/dist/namespaces/storage.d.ts +3 -3
  259. package/dist/namespaces/storage.js +10 -10
  260. package/dist/native-knowledge.d.ts +1 -1
  261. package/dist/offline-sync-impression-drain.d.ts +1 -1
  262. package/dist/operator-doctor-corpus.d.ts +1 -1
  263. package/dist/operator-doctor-corpus.js +13 -13
  264. package/dist/operator-doctor-replica.d.ts +1 -1
  265. package/dist/operator-toolkit.d.ts +3 -3
  266. package/dist/operator-toolkit.js +19 -19
  267. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  268. package/dist/orchestration/maintenance.d.ts +4 -4
  269. package/dist/orchestration/maintenance.js +16 -16
  270. package/dist/{orchestrator-B3ZQ8CTJ.d.ts → orchestrator-6fPa2l9x.d.ts} +8 -8
  271. package/dist/orchestrator.d.ts +9 -9
  272. package/dist/orchestrator.js +70 -68
  273. package/dist/patterns-cli.d.ts +1 -1
  274. package/dist/{pipeline-q5emoXD6.d.ts → pipeline-DHpYtfM3.d.ts} +1 -1
  275. package/dist/policy-runtime.d.ts +1 -1
  276. package/dist/proactive-contention.d.ts +1 -1
  277. package/dist/provenance.d.ts +1 -1
  278. package/dist/{public-http-CQN-i7Ai.d.ts → public-http-BT36aBb-.d.ts} +1 -1
  279. package/dist/{qmd-YC6YGphs.d.ts → qmd-BaX31tM0.d.ts} +1 -1
  280. package/dist/qmd-preflight.d.ts +2 -2
  281. package/dist/qmd-recall-cache.d.ts +1 -1
  282. package/dist/qmd.d.ts +2 -2
  283. package/dist/recall-concurrency-config.d.ts +1 -1
  284. package/dist/recall-disclosure-escalation.d.ts +1 -1
  285. package/dist/recall-explain-renderer.d.ts +1 -1
  286. package/dist/recall-explain-renderer.js +3 -3
  287. package/dist/recall-memory-map.d.ts +1 -1
  288. package/dist/recall-planner-llm.d.ts +1 -1
  289. package/dist/recall-state.d.ts +1 -1
  290. package/dist/recall-tag-filter.d.ts +1 -1
  291. package/dist/recall-timings.d.ts +1 -1
  292. package/dist/recall-xray-cli.d.ts +1 -1
  293. package/dist/recall-xray-cli.js +4 -4
  294. package/dist/recall-xray-renderer.d.ts +1 -1
  295. package/dist/recall-xray-renderer.js +3 -3
  296. package/dist/recall-xray.d.ts +1 -1
  297. package/dist/recall-xray.js +2 -2
  298. package/dist/reconcile/cursor.d.ts +1 -1
  299. package/dist/reconcile/manifest.d.ts +1 -1
  300. package/dist/reconcile/plan.d.ts +1 -1
  301. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  302. package/dist/replay/normalizers/claude.d.ts +1 -1
  303. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  304. package/dist/replay/normalizers/shared.d.ts +1 -1
  305. package/dist/replay/runner.d.ts +1 -1
  306. package/dist/replay/types.d.ts +1 -1
  307. package/dist/replica-divergence.d.ts +1 -1
  308. package/dist/replica-peers-config.d.ts +1 -1
  309. package/dist/resolve-auth-token.d.ts +1 -1
  310. package/dist/resume-bundles.js +2 -2
  311. package/dist/retrieval-agents.d.ts +2 -2
  312. package/dist/retrieval-tiers.d.ts +1 -1
  313. package/dist/routing/engine.d.ts +1 -1
  314. package/dist/routing/store.d.ts +1 -1
  315. package/dist/salvage-envelope.d.ts +1 -1
  316. package/dist/schemas.d.ts +108 -108
  317. package/dist/{scope-profiles-De8F7YQ6.d.ts → scope-profiles-1hYWJ9bM.d.ts} +1 -1
  318. package/dist/search/embed-helper.d.ts +1 -1
  319. package/dist/search/factory.d.ts +1 -1
  320. package/dist/search/factory.js +6 -6
  321. package/dist/search/index.d.ts +1 -1
  322. package/dist/search/index.js +10 -10
  323. package/dist/search/lancedb-backend.d.ts +1 -1
  324. package/dist/search/lancedb-backend.js +2 -2
  325. package/dist/search/meilisearch-backend.d.ts +1 -1
  326. package/dist/search/meilisearch-backend.js +2 -2
  327. package/dist/search/noop-backend.d.ts +1 -1
  328. package/dist/search/orama-backend.d.ts +1 -1
  329. package/dist/search/orama-backend.js +2 -2
  330. package/dist/search/port.d.ts +1 -1
  331. package/dist/search/remote-backend.d.ts +1 -1
  332. package/dist/{semantic-consolidation-Bf-hm2LQ.d.ts → semantic-consolidation-Chmddvcg.d.ts} +1 -1
  333. package/dist/semantic-consolidation.d.ts +2 -2
  334. package/dist/semantic-consolidation.js +11 -11
  335. package/dist/semantic-rule-promotion.js +10 -10
  336. package/dist/semantic-rule-verifier.d.ts +1 -1
  337. package/dist/semantic-rule-verifier.js +10 -10
  338. package/dist/{service-D83osSRn.d.ts → service-DMs_dCCg.d.ts} +2 -2
  339. package/dist/session-observer-bands.d.ts +1 -1
  340. package/dist/session-observer-state.d.ts +1 -1
  341. package/dist/shared-context/manager.d.ts +9 -9
  342. package/dist/signal.d.ts +1 -1
  343. package/dist/source-agent-qualifier.d.ts +1 -1
  344. package/dist/source-agent-qualifier.js +10 -10
  345. package/dist/{storage-BHWbpbhj.d.ts → storage-CzorrpcN.d.ts} +12 -3
  346. package/dist/storage.d.ts +2 -2
  347. package/dist/storage.js +9 -9
  348. package/dist/summarizer.d.ts +1 -1
  349. package/dist/summary-snapshot.d.ts +1 -1
  350. package/dist/support-passport/index.d.ts +15 -14
  351. package/dist/support-passport/index.js +26 -25
  352. package/dist/temporal-supersession.d.ts +2 -2
  353. package/dist/temporal-timeline-recall.d.ts +1 -1
  354. package/dist/temporal-validity.d.ts +1 -1
  355. package/dist/threading.d.ts +1 -1
  356. package/dist/tier-migration.d.ts +2 -2
  357. package/dist/tier-routing.d.ts +1 -1
  358. package/dist/topics.d.ts +1 -1
  359. package/dist/transcript.d.ts +1 -1
  360. package/dist/transfer/backup.js +2 -2
  361. package/dist/transfer/capsule-export.js +2 -2
  362. package/dist/transfer/capsule-import.js +2 -2
  363. package/dist/transfer/types.d.ts +66 -66
  364. package/dist/trust-score-stage.d.ts +1 -1
  365. package/dist/trust-score.d.ts +1 -1
  366. package/dist/{types-CpgsN-Gb.d.ts → types-BP5MC8UU.d.ts} +10 -5
  367. package/dist/types.d.ts +1 -1
  368. package/dist/types.js +1 -1
  369. package/dist/utility-runtime.d.ts +1 -1
  370. package/dist/verified-recall.js +10 -10
  371. package/dist/who-knows-cli.d.ts +155 -0
  372. package/dist/who-knows-cli.js +109 -0
  373. package/dist/who-knows-cli.js.map +1 -0
  374. package/dist/who-knows.d.ts +127 -0
  375. package/dist/who-knows.js +118 -0
  376. package/dist/who-knows.js.map +1 -0
  377. package/dist/write-envelope.d.ts +1 -1
  378. package/package.json +2 -2
  379. package/src/access-boundary.ts +1 -0
  380. package/src/access-http.ts +15 -0
  381. package/src/access-mcp-output-schemas.ts +1 -0
  382. package/src/access-mcp.ts +3 -2
  383. package/src/access-operations-batch.ts +8 -0
  384. package/src/access-service.ts +26 -0
  385. package/src/access-surface-catalog.ts +2 -0
  386. package/src/cli/okf-commands.ts +23 -0
  387. package/src/cli.ts +2 -1
  388. package/src/config.ts +2 -1
  389. package/src/index.ts +3 -0
  390. package/src/mcp-read-only-tools.ts +1 -0
  391. package/src/okf/cli.ts +42 -0
  392. package/src/okf/config.ts +34 -0
  393. package/src/okf/lint.ts +110 -0
  394. package/src/okf/sweep.ts +63 -0
  395. package/src/okf/type-mapping.test.ts +207 -0
  396. package/src/okf/type-mapping.ts +79 -0
  397. package/src/orchestrator.ts +2 -0
  398. package/src/storage/entity-store.ts +9 -5
  399. package/src/storage/profile-header.ts +33 -9
  400. package/src/storage/questions-file.ts +50 -0
  401. package/src/storage.ts +76 -63
  402. package/src/types.ts +4 -5
  403. package/src/who-knows-cli.ts +96 -0
  404. package/src/who-knows.test.ts +257 -0
  405. package/src/who-knows.ts +364 -0
  406. package/dist/chunk-2RZCOG63.js.map +0 -1
  407. package/dist/chunk-4KKVDOCD.js.map +0 -1
  408. package/dist/chunk-7D3TTGHY.js.map +0 -1
  409. package/dist/chunk-AL43367I.js.map +0 -1
  410. package/dist/chunk-DPPTG6VJ.js.map +0 -1
  411. package/dist/chunk-FMGTFMT3.js.map +0 -1
  412. package/dist/chunk-HIEWDJ2O.js.map +0 -1
  413. package/dist/chunk-IPUUMLSM.js.map +0 -1
  414. package/dist/chunk-VD2OT77P.js.map +0 -1
  415. package/dist/chunk-XFAWGUNX.js.map +0 -1
  416. /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  417. /package/dist/{chunk-D267K4V5.js.map → chunk-246HFJVG.js.map} +0 -0
  418. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  419. /package/dist/{chunk-WNO5W2TP.js.map → chunk-3A46U7XG.js.map} +0 -0
  420. /package/dist/{chunk-5Q5BEWJ3.js.map → chunk-44DSGZFF.js.map} +0 -0
  421. /package/dist/{chunk-VENMDW4U.js.map → chunk-55SJFHVD.js.map} +0 -0
  422. /package/dist/{chunk-VCBG52MM.js.map → chunk-5HERQ5FX.js.map} +0 -0
  423. /package/dist/{chunk-IRXMJLTW.js.map → chunk-5HK3CJAR.js.map} +0 -0
  424. /package/dist/{chunk-VSKA2ATE.js.map → chunk-6LTCUN3T.js.map} +0 -0
  425. /package/dist/{chunk-IHPYTG6F.js.map → chunk-7BVO64UC.js.map} +0 -0
  426. /package/dist/{chunk-ZUIVJ3BB.js.map → chunk-7V3AZWWZ.js.map} +0 -0
  427. /package/dist/{chunk-Y5ZIQFWH.js.map → chunk-A2IBK3TG.js.map} +0 -0
  428. /package/dist/{chunk-6I6IOMKS.js.map → chunk-C6AKYOFO.js.map} +0 -0
  429. /package/dist/{chunk-ZGGTIBWN.js.map → chunk-CKZHKFY3.js.map} +0 -0
  430. /package/dist/{chunk-JE3AKPOO.js.map → chunk-DG6MJD7W.js.map} +0 -0
  431. /package/dist/{chunk-P4SD7BSA.js.map → chunk-DHKV6E4A.js.map} +0 -0
  432. /package/dist/{chunk-KOE3RLNN.js.map → chunk-EJDLQRUO.js.map} +0 -0
  433. /package/dist/{chunk-DRS3OX77.js.map → chunk-GVRLED7R.js.map} +0 -0
  434. /package/dist/{chunk-EAWJSY2Z.js.map → chunk-H5CGAD7L.js.map} +0 -0
  435. /package/dist/{chunk-DLZBOEJY.js.map → chunk-IFK3PVM7.js.map} +0 -0
  436. /package/dist/{chunk-E7MYSCSL.js.map → chunk-IJXECCUV.js.map} +0 -0
  437. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  438. /package/dist/{chunk-NMQXUGTU.js.map → chunk-JCSUNXYJ.js.map} +0 -0
  439. /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
  440. /package/dist/{chunk-YY63AMGP.js.map → chunk-KRPE7ANR.js.map} +0 -0
  441. /package/dist/{chunk-AGC3Q53R.js.map → chunk-LOETWOEB.js.map} +0 -0
  442. /package/dist/{chunk-7X35RPN3.js.map → chunk-LZKCMK3U.js.map} +0 -0
  443. /package/dist/{chunk-K547KS3X.js.map → chunk-ML3U4KPR.js.map} +0 -0
  444. /package/dist/{chunk-CI6YIT5S.js.map → chunk-NO7B6DRB.js.map} +0 -0
  445. /package/dist/{chunk-Q3IRE6E4.js.map → chunk-ON2P3SHG.js.map} +0 -0
  446. /package/dist/{chunk-2Q4UAPLD.js.map → chunk-OQIRBGUU.js.map} +0 -0
  447. /package/dist/{chunk-WDFXRHOZ.js.map → chunk-PE76XIDM.js.map} +0 -0
  448. /package/dist/{chunk-MVIYRRVJ.js.map → chunk-PMVPCZJZ.js.map} +0 -0
  449. /package/dist/{chunk-VCNGVU62.js.map → chunk-PVIXAMWM.js.map} +0 -0
  450. /package/dist/{chunk-QHNO6AND.js.map → chunk-RWLRKMZK.js.map} +0 -0
  451. /package/dist/{chunk-UWSETW6O.js.map → chunk-SB6YBB2V.js.map} +0 -0
  452. /package/dist/{chunk-DTQ3OB7O.js.map → chunk-SHQFYZTD.js.map} +0 -0
  453. /package/dist/{chunk-G5OB4XZX.js.map → chunk-VANZKHTN.js.map} +0 -0
  454. /package/dist/{chunk-3CYVTO6A.js.map → chunk-VUP47UJI.js.map} +0 -0
  455. /package/dist/{chunk-OTVHRZZD.js.map → chunk-WBM5NR4E.js.map} +0 -0
  456. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  457. /package/dist/{chunk-B2GICNOJ.js.map → chunk-XH4IQVTN.js.map} +0 -0
  458. /package/dist/{chunk-OWAYQK5V.js.map → chunk-XZUCNVGE.js.map} +0 -0
  459. /package/dist/{chunk-4FSCGYTQ.js.map → chunk-YFQTDE7N.js.map} +0 -0
  460. /package/dist/{chunk-RMB22Q3R.js.map → chunk-YXGU3MDQ.js.map} +0 -0
  461. /package/dist/{chunk-XSPRZZEX.js.map → chunk-Z6CJLYXT.js.map} +0 -0
  462. /package/dist/{chunk-DZ2AVS2H.js.map → chunk-ZSYE2QVJ.js.map} +0 -0
@@ -0,0 +1,257 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { EngramAccessInputError } from "./access-errors.js";
5
+ import { EngramAccessService } from "./access-service.js";
6
+ import type { MemoryFile, PluginConfig } from "./types.js";
7
+ import {
8
+ computeWhoKnows,
9
+ handleWhoKnowsHttpQuery,
10
+ validateWhoKnowsInput,
11
+ type WhoKnowsEntity,
12
+ type WhoKnowsStorage,
13
+ } from "./who-knows.js";
14
+ import { extractWhoKnowsRawArgs, parseWhoKnowsCliOptions } from "./who-knows-cli.js";
15
+
16
+ function bodyCode(body: unknown): string | undefined {
17
+ return body && typeof body === "object" && "code" in body && typeof body.code === "string"
18
+ ? body.code
19
+ : undefined;
20
+ }
21
+
22
+ function mem(overrides: {
23
+ id: string;
24
+ content: string;
25
+ entityRef?: string;
26
+ updated?: string;
27
+ importance?: number;
28
+ confidence?: number;
29
+ status?: string;
30
+ }): MemoryFile {
31
+ return {
32
+ path: `/synthetic/mem/facts/2026-08-17/${overrides.id}.md`,
33
+ frontmatter: {
34
+ id: overrides.id,
35
+ category: "fact",
36
+ created: overrides.updated ?? "2026-08-01T00:00:00Z",
37
+ updated: overrides.updated ?? "2026-08-01T00:00:00Z",
38
+ source: "extraction",
39
+ confidence: overrides.confidence ?? 0.8,
40
+ tags: [],
41
+ ...(overrides.entityRef ? { entityRef: overrides.entityRef } : {}),
42
+ ...(overrides.importance !== undefined ? { importance: { score: overrides.importance, reason: "test" } } : {}),
43
+ ...(overrides.status ? { status: overrides.status } : {}),
44
+ confidenceTier: "inferred",
45
+ } as MemoryFile["frontmatter"],
46
+ content: overrides.content,
47
+ };
48
+ }
49
+
50
+ function fakeStorage(memories: MemoryFile[], entities: WhoKnowsEntity[] = []): WhoKnowsStorage & { readEntityRaw?: unknown } {
51
+ return {
52
+ async readAllMemories() {
53
+ return memories;
54
+ },
55
+ async listEntityNames() {
56
+ return entities.map((entity) => entity.id);
57
+ },
58
+ async readEntity(name) {
59
+ const entity = entities.find((candidate) => candidate.id === name);
60
+ return entity
61
+ ? `---\nname: ${entity.name}\ntype: person\nupdated: 2026-08-01T00:00:00Z\naliases: [${entity.aliases.map((a) => `"${a}"`).join(", ")}]\n---\n`
62
+ : "";
63
+ },
64
+ };
65
+ }
66
+
67
+ test("scoring ties: recency breaks ties, entity id is the stable final key", () => {
68
+ const entities: WhoKnowsEntity[] = [
69
+ { id: "person-alice", name: "Alice", aliases: [] },
70
+ { id: "person-bob", name: "Bob", aliases: [] },
71
+ ];
72
+ // Equal weights: same coverage, importance, confidence — one memory each.
73
+ const memories = [
74
+ mem({ id: "m-alice", content: "kafka consumer group tuning notes", entityRef: "person-alice", updated: "2026-08-01T00:00:00Z" }),
75
+ mem({ id: "m-bob", content: "kafka partition rebalance notes", entityRef: "person-bob", updated: "2026-08-10T00:00:00Z" }),
76
+ ];
77
+ const byRecency = computeWhoKnows({ topic: "kafka", limit: 5, memories, entities });
78
+ assert.equal(byRecency.results[0]?.entityId, "person-bob", "newer evidence wins a score tie");
79
+ assert.equal(byRecency.results[0]?.score, 1, "top hit normalizes to 1");
80
+ assert.ok(byRecency.results[0]?.score >= (byRecency.results[1]?.score ?? 0));
81
+
82
+ // Full tie (same score, same lastSeen) falls back to entity id ascending.
83
+ const tied = computeWhoKnows({
84
+ topic: "kafka",
85
+ limit: 5,
86
+ memories: [
87
+ mem({ id: "m-bob-tie", content: "kafka partition rebalance notes", entityRef: "person-bob", updated: "2026-08-01T00:00:00Z" }),
88
+ mem({ id: "m-alice-tie", content: "kafka consumer group tuning notes", entityRef: "person-alice", updated: "2026-08-01T00:00:00Z" }),
89
+ ],
90
+ entities,
91
+ });
92
+ assert.deepEqual(tied.results.map((hit) => hit.entityId), ["person-alice", "person-bob"]);
93
+ // Deterministic: repeated invocations produce identical output.
94
+ const again = computeWhoKnows({ topic: "kafka", limit: 5, memories, entities });
95
+ assert.deepEqual(
96
+ again.results.map((hit) => hit.entityId),
97
+ byRecency.results.map((hit) => hit.entityId),
98
+ );
99
+ });
100
+
101
+ test("missing entity names: entityRef without an entity file still ranks, name is null", () => {
102
+ const memories = [mem({ id: "m-ghost", content: "kafka exactly-once semantics deep dive", entityRef: "person-ghost" })];
103
+ const result = computeWhoKnows({ topic: "kafka", limit: 5, memories, entities: [] });
104
+ assert.equal(result.results.length, 1);
105
+ assert.equal(result.results[0]?.entityId, "person-ghost");
106
+ assert.equal(result.results[0]?.entityName, null);
107
+ assert.ok(result.results[0]?.evidence[0]?.path.endsWith("m-ghost.md"));
108
+ });
109
+
110
+ test("zero-evidence topic returns an empty list, not an error", () => {
111
+ const memories = [mem({ id: "m-unrelated", content: "unrelated quarterly planning notes", entityRef: "person-alice" })];
112
+ const entities: WhoKnowsEntity[] = [{ id: "person-alice", name: "Alice", aliases: [] }];
113
+ const result = computeWhoKnows({ topic: "kubernetes", limit: 5, memories, entities });
114
+ assert.deepEqual(result, { topic: "kubernetes", results: [] });
115
+ });
116
+
117
+ test("non-active memories are not evidence", () => {
118
+ const entities: WhoKnowsEntity[] = [{ id: "person-alice", name: "Alice", aliases: [] }];
119
+ const memories = [
120
+ mem({ id: "m-sup", content: "kafka notes superseded", entityRef: "person-alice", status: "superseded" }),
121
+ mem({ id: "m-ok", content: "kafka notes active", entityRef: "person-alice" }),
122
+ ];
123
+ const result = computeWhoKnows({ topic: "kafka", limit: 5, memories, entities });
124
+ assert.equal(result.results.length, 1);
125
+ assert.equal(result.results[0]?.evidenceCount, 1);
126
+ assert.equal(result.results[0]?.evidence[0]?.id, "m-ok");
127
+ });
128
+
129
+ test("authorship bonus is per entity: co-mentioned entity does not get the speaker bonus", () => {
130
+ const entities: WhoKnowsEntity[] = [
131
+ { id: "person-alice", name: "Alice", aliases: [] },
132
+ { id: "person-bob", name: "Bob", aliases: [] },
133
+ ];
134
+ const memories = [
135
+ mem({ id: "m-attr", content: "Alice explained the kafka rebalance with Bob", updated: "2026-08-10T00:00:00Z" }),
136
+ ];
137
+ const result = computeWhoKnows({ topic: "kafka", limit: 5, memories, entities });
138
+ const alice = result.results.find((hit) => hit.entityId === "person-alice");
139
+ const bob = result.results.find((hit) => hit.entityId === "person-bob");
140
+ assert.ok(alice && bob, "both co-mentioned entities rank");
141
+ assert.ok((alice?.score ?? 0) > (bob?.score ?? 0), "attributed speaker outranks the co-mentioned entity");
142
+ assert.match(alice?.rationale ?? "", /with direct attribution/);
143
+ assert.doesNotMatch(bob?.rationale ?? "", /with direct attribution/);
144
+ });
145
+
146
+ test("empty topic and invalid limit are validation errors", () => {
147
+ assert.throws(() => validateWhoKnowsInput(" ", 5), /whoKnows: topic is required/);
148
+ assert.throws(() => validateWhoKnowsInput("kafka", 0), /whoKnows: limit/);
149
+ assert.throws(() => validateWhoKnowsInput("kafka", 51), /whoKnows: limit/);
150
+ });
151
+
152
+ test("empty topic 400: HTTP helper maps missing/invalid query params to 400 without calling run", async () => {
153
+ let runCalls = 0;
154
+ const outcome = await handleWhoKnowsHttpQuery({
155
+ getParam: (name) => (name === "topic" ? " " : null),
156
+ resolveNamespace: (namespace) => namespace,
157
+ run: async () => {
158
+ runCalls += 1;
159
+ throw new Error("must not run");
160
+ },
161
+ });
162
+ assert.equal(outcome.status, 400);
163
+ assert.equal(bodyCode(outcome.body), "missing_topic");
164
+ assert.equal(runCalls, 0);
165
+
166
+ const badLimit = await handleWhoKnowsHttpQuery({
167
+ getParam: (name) => (name === "topic" ? "kafka" : name === "limit" ? "abc" : null),
168
+ resolveNamespace: (namespace) => namespace,
169
+ run: async () => {
170
+ runCalls += 1;
171
+ throw new Error("must not run");
172
+ },
173
+ });
174
+ assert.equal(badLimit.status, 400);
175
+ assert.equal(bodyCode(badLimit.body), "invalid_limit");
176
+ assert.equal(runCalls, 0);
177
+
178
+ const happy = await handleWhoKnowsHttpQuery({
179
+ getParam: (name) => (name === "topic" ? "kafka" : name === "limit" ? "3" : name === "namespace" ? "team" : null),
180
+ resolveNamespace: (namespace) => namespace,
181
+ run: async (request) => {
182
+ runCalls += 1;
183
+ assert.deepEqual(request, { topic: "kafka", limit: 3, namespace: "team" });
184
+ return { topic: "kafka", results: [] };
185
+ },
186
+ });
187
+ assert.equal(happy.status, 200);
188
+ assert.equal(runCalls, 1);
189
+ });
190
+
191
+ test("namespace allow-list gate runs even when the namespace param is omitted", async () => {
192
+ const resolved: Array<string | undefined> = [];
193
+ const outcome = await handleWhoKnowsHttpQuery({
194
+ getParam: (name) => (name === "topic" ? "kafka" : null),
195
+ resolveNamespace: (namespace) => {
196
+ resolved.push(namespace);
197
+ return "default";
198
+ },
199
+ run: async (request) => {
200
+ assert.equal(request.namespace, "default", "resolved default namespace flows into the request");
201
+ return { topic: "kafka", results: [] };
202
+ },
203
+ });
204
+ assert.equal(outcome.status, 200);
205
+ assert.deepEqual(resolved, [undefined], "resolver (and its allow-list) ran once with the implicit namespace");
206
+ });
207
+
208
+ test("namespace restriction: storage is resolved per readable namespace and the ACL matches recall", async () => {
209
+ const config = {
210
+ namespacesEnabled: true,
211
+ defaultNamespace: "default",
212
+ namespacePolicies: [{ name: "team", readPrincipals: ["reader"], writePrincipals: ["writer"] }],
213
+ memoryDir: "/synthetic/mem",
214
+ } as unknown as PluginConfig;
215
+ const getStorageNames: string[] = [];
216
+ const teamStorage = fakeStorage(
217
+ [mem({ id: "m-team", content: "kafka consumer tuning deep dive", entityRef: "person-alice" })],
218
+ [{ id: "person-alice", name: "Alice", aliases: [] }],
219
+ );
220
+ const defaultStorage = fakeStorage([mem({ id: "m-default", content: "kafka other notes", entityRef: "person-carol" })]);
221
+ const service = Object.create(EngramAccessService.prototype) as EngramAccessService;
222
+ (service as unknown as { orchestrator: unknown }).orchestrator = {
223
+ config,
224
+ getStorage: async (namespace: string) => {
225
+ getStorageNames.push(namespace);
226
+ return namespace === "team" ? teamStorage : defaultStorage;
227
+ },
228
+ };
229
+
230
+ const allowed = await service.whoKnows({ topic: "kafka", namespace: "team", authenticatedPrincipal: "reader" });
231
+ assert.equal(allowed.results.length, 1);
232
+ assert.equal(allowed.results[0]?.entityId, "person-alice");
233
+ assert.deepEqual(getStorageNames, ["team"], "reads only the resolved namespace's storage — no cross-namespace leak");
234
+
235
+ await assert.rejects(
236
+ () => service.whoKnows({ topic: "kafka", namespace: "team" }),
237
+ (err: unknown) => err instanceof EngramAccessInputError && /authentication required/.test(err.message),
238
+ "no principal + namespaces enabled must fail closed",
239
+ );
240
+
241
+ await assert.rejects(
242
+ () => service.whoKnows({ topic: "kafka", namespace: "team", authenticatedPrincipal: "nobody" }),
243
+ (err: unknown) => err instanceof EngramAccessInputError && /not readable/.test(err.message),
244
+ "principal outside readPrincipals must be denied",
245
+ );
246
+ });
247
+
248
+ test("CLI parsing: empty topic, bare --limit, and unknown flags throw listed-options errors", () => {
249
+ const { topic, options } = extractWhoKnowsRawArgs(["kafka", "tuning", "--limit", "3", "--json"]);
250
+ const parsed = parseWhoKnowsCliOptions(topic, options);
251
+ assert.deepEqual(parsed, { topic: "kafka tuning", limit: 3, namespace: undefined, json: true });
252
+
253
+ assert.throws(() => parseWhoKnowsCliOptions("", {}), /topic is required/);
254
+ assert.throws(() => extractWhoKnowsRawArgs(["kafka", "--limit"]), /--limit expects a value/);
255
+ assert.throws(() => extractWhoKnowsRawArgs(["kafka", "--bogus"]), /unknown option/);
256
+ assert.throws(() => parseWhoKnowsCliOptions("kafka", { limit: 0 }), /--limit expects an integer/);
257
+ });
@@ -0,0 +1,364 @@
1
+ /**
2
+ * `who_knows` — rank entities by demonstrated expertise for a topic
3
+ * (issue #2057).
4
+ *
5
+ * Deterministic scoring over existing entity + fact data. No LLM, no QMD:
6
+ * a single pass over the namespace-scoped corpus (`readAllMemories`) plus
7
+ * entity files. Signals:
8
+ *
9
+ * 1. Entity mentions co-occurring with topic tokens (entityRef + name/alias
10
+ * mention in matching memory content).
11
+ * 2. Speaker/authorship attribution ("Alice said/explained/…") when present —
12
+ * small multiplicative bonus.
13
+ * 3. Recency as a tie-break only (per the issue): newer evidence ranks higher
14
+ * when scores tie; entity id is the final stable key (total order).
15
+ * 4. Existing importance + confidence frontmatter weight each evidence item.
16
+ *
17
+ * Pure functions live here so tests can exercise scoring without booting an
18
+ * orchestrator; the access service supplies namespace-scoped data. MCP tool
19
+ * definitions are exported for access-mcp.ts to spread into `tools/list`
20
+ * (same pattern as `meetings/mcp-tools.ts`).
21
+ */
22
+
23
+ import { EngramAccessInputError } from "./access-errors.js";
24
+ import type { McpTool } from "./access-mcp.js";
25
+ import { inferMemoryStatus } from "./memory-lifecycle-ledger-utils.js";
26
+ import { parseEntityFile } from "./storage/entity-store.js";
27
+ import type { MemoryFile, PluginConfig } from "./types.js";
28
+
29
+ /** Default result count (issue #2057: "small ranked list"). */
30
+ export const WHO_KNOWS_DEFAULT_LIMIT = 5;
31
+ /** Upper bound for `limit`. Rejects unbounded fan-out without silently clamping. */
32
+ export const WHO_KNOWS_MAX_LIMIT = 50;
33
+ /** Evidence refs retained per hit. Independent of the result limit. */
34
+ export const WHO_KNOWS_MAX_EVIDENCE = 50;
35
+ /** Multiplicative bonus for evidence with direct speaker attribution. */
36
+ export const WHO_KNOWS_AUTHORSHIP_BONUS = 1.5;
37
+
38
+ /** Minimal storage surface `who_knows` needs — satisfied by `StorageManager`. */
39
+ export interface WhoKnowsStorage {
40
+ readAllMemories(): Promise<MemoryFile[]>;
41
+ listEntityNames(): Promise<string[]>;
42
+ readEntity(name: string): Promise<string>;
43
+ }
44
+
45
+ /** Flattened entity input for scoring. `id` is the entity key (file stem). */
46
+ export interface WhoKnowsEntity {
47
+ id: string;
48
+ name: string;
49
+ aliases: readonly string[];
50
+ }
51
+
52
+ /** Evidence reference — ids/paths/timestamps only, no raw content. */
53
+ export interface WhoKnowsEvidence {
54
+ id: string;
55
+ path: string;
56
+ updated: string;
57
+ }
58
+
59
+ export interface WhoKnowsHit {
60
+ entityId: string;
61
+ entityName: string | null;
62
+ /** Normalized 0–1 relative to the top hit (top = 1). */
63
+ score: number;
64
+ rationale: string;
65
+ evidenceCount: number;
66
+ lastSeen: string | null;
67
+ evidence: WhoKnowsEvidence[];
68
+ }
69
+
70
+ export interface WhoKnowsResult {
71
+ topic: string;
72
+ results: WhoKnowsHit[];
73
+ }
74
+
75
+ /** Load all entities for a namespace-scoped storage as scoring input. */
76
+ export async function loadWhoKnowsEntities(
77
+ storage: WhoKnowsStorage,
78
+ entitySchemas?: PluginConfig["entitySchemas"],
79
+ ): Promise<WhoKnowsEntity[]> {
80
+ const entities: WhoKnowsEntity[] = [];
81
+ for (const id of await storage.listEntityNames()) {
82
+ const raw = await storage.readEntity(id);
83
+ if (!raw) continue;
84
+ try {
85
+ const entity = parseEntityFile(raw, entitySchemas);
86
+ entities.push({ id, name: entity.name, aliases: entity.aliases });
87
+ } catch {
88
+ // Unparseable entity files are skipped, not fatal — same tolerance as
89
+ // the entityList surface (a bad file must not break ranking).
90
+ }
91
+ }
92
+ return entities;
93
+ }
94
+
95
+ /** Validate topic + limit at every entrypoint. Shared by service/CLI/HTTP. */
96
+ export function validateWhoKnowsInput(topic: string, limit: number): void {
97
+ if (topic.trim().length === 0) {
98
+ throw new EngramAccessInputError("whoKnows: topic is required and must be non-empty");
99
+ }
100
+ if (!Number.isInteger(limit) || limit < 1 || limit > WHO_KNOWS_MAX_LIMIT) {
101
+ throw new EngramAccessInputError(
102
+ `whoKnows: limit expects an integer between 1 and ${WHO_KNOWS_MAX_LIMIT}`,
103
+ );
104
+ }
105
+ }
106
+ const STOPWORDS: Record<string, true> = {
107
+ the: true, a: true, an: true, of: true, and: true, or: true, to: true, in: true, for: true,
108
+ on: true, with: true, is: true, are: true, was: true, were: true, who: true, knows: true,
109
+ know: true, about: true, at: true, by: true,
110
+ };
111
+
112
+ /** Lowercase alphanumeric topic tokens, stopwords dropped, deduplicated. */
113
+ export function tokenizeTopic(topic: string): string[] {
114
+ const tokens = topic
115
+ .toLowerCase()
116
+ .split(/[^a-z0-9]+/)
117
+ .filter((token) => token.length >= 2 && STOPWORDS[token] !== true);
118
+ return [...new Set(tokens)];
119
+ }
120
+
121
+ const ATTRIBUTION_VERBS =
122
+ "said|says|mentioned|noted|explained|recommended|suggested|proposed|wrote|asked|answered|demonstrated|designed|built|debugged|fixed";
123
+
124
+ function escapeRegex(text: string): string {
125
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
126
+ }
127
+
128
+ function wordMatcher(term: string): RegExp {
129
+ return new RegExp(`\\b${escapeRegex(term.toLowerCase())}\\b`);
130
+ }
131
+
132
+ interface ScoredCandidate {
133
+ entityId: string;
134
+ entityName: string | null;
135
+ rawScore: number;
136
+ evidence: WhoKnowsEvidence[];
137
+ matchCount: number;
138
+ authorshipCount: number;
139
+ lastSeen: string | null;
140
+ }
141
+
142
+ /**
143
+ * Deterministic ranking. Pure: same inputs → same output, total order via
144
+ * (score desc, lastSeen desc, entityId asc) so ties never depend on scan
145
+ * order (sort-stability rule).
146
+ */
147
+ export function computeWhoKnows(input: {
148
+ topic: string;
149
+ limit: number;
150
+ memories: readonly MemoryFile[];
151
+ entities: readonly WhoKnowsEntity[];
152
+ }): WhoKnowsResult {
153
+ const topicTokens = tokenizeTopic(input.topic);
154
+ const byId = new Map(input.entities.map((entity) => [entity.id, entity]));
155
+
156
+ // Multi-word names/aliases must match as phrases; single words as tokens.
157
+ const mentionMatchers: Array<{ entityId: string; matcher: RegExp }> = [];
158
+ const authorshipMatchers: Array<{ entityId: string; matcher: RegExp }> = [];
159
+ for (const entity of input.entities) {
160
+ for (const term of [entity.name, ...entity.aliases]) {
161
+ const clean = term.trim().toLowerCase();
162
+ if (clean.length < 2) continue;
163
+ mentionMatchers.push({ entityId: entity.id, matcher: wordMatcher(clean) });
164
+ const possessive = clean.replace(/'/g, "");
165
+ authorshipMatchers.push({
166
+ entityId: entity.id,
167
+ matcher: new RegExp(
168
+ `\\b${escapeRegex(possessive)}(?:'s)?\\b[^.!?]{0,40}\\b(?:${ATTRIBUTION_VERBS})\\b`,
169
+ ),
170
+ });
171
+ }
172
+ }
173
+
174
+ const candidates = new Map<string, ScoredCandidate>();
175
+ const credit = (entityId: string, memory: MemoryFile, weight: number, authorship: boolean): void => {
176
+ let candidate = candidates.get(entityId);
177
+ if (!candidate) {
178
+ candidate = {
179
+ entityId,
180
+ entityName: byId.get(entityId)?.name ?? null,
181
+ rawScore: 0,
182
+ evidence: [],
183
+ matchCount: 0,
184
+ authorshipCount: 0,
185
+ lastSeen: null,
186
+ };
187
+ candidates.set(entityId, candidate);
188
+ }
189
+ candidate.rawScore += weight;
190
+ candidate.matchCount += 1;
191
+ candidate.authorshipCount += authorship ? 1 : 0;
192
+ const updated = memory.frontmatter.updated || memory.frontmatter.created;
193
+ if (updated && (!candidate.lastSeen || updated > candidate.lastSeen)) {
194
+ candidate.lastSeen = updated;
195
+ }
196
+ if (candidate.evidence.length < WHO_KNOWS_MAX_EVIDENCE) {
197
+ candidate.evidence.push({
198
+ id: memory.frontmatter.id,
199
+ path: memory.path,
200
+ updated: memory.frontmatter.updated || memory.frontmatter.created,
201
+ });
202
+ }
203
+ };
204
+
205
+ for (const memory of input.memories) {
206
+ if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
207
+ const haystack = `${memory.content}\n${memory.frontmatter.tags.join(" ")}`.toLowerCase();
208
+ const matched = topicTokens.filter((token) => haystack.includes(token));
209
+ if (matched.length === 0) continue;
210
+ const coverage = matched.length / topicTokens.length;
211
+ const importance = memory.frontmatter.importance?.score ?? 0.5;
212
+ const confidence = memory.frontmatter.confidence ?? 0.5;
213
+ let weight = coverage * (0.5 + 0.5 * importance) * confidence;
214
+
215
+ const attributed = new Set<string>();
216
+ // entityRef counts even when the entity file is missing — the hit then
217
+ // carries the ref id with a null display name (missing-name case).
218
+ if (memory.frontmatter.entityRef) attributed.add(memory.frontmatter.entityRef);
219
+ for (const { entityId, matcher } of mentionMatchers) {
220
+ if (matcher.test(haystack)) attributed.add(entityId);
221
+ }
222
+ if (attributed.size === 0) continue;
223
+
224
+ // Attribution is per entity: "Alice explained X with Bob" credits only
225
+ // Alice with authorship — Bob is a co-mention, not the speaker.
226
+ const attributedAuthors = new Set<string>();
227
+ for (const { entityId, matcher } of authorshipMatchers) {
228
+ if (attributed.has(entityId) && matcher.test(haystack)) attributedAuthors.add(entityId);
229
+ }
230
+ for (const entityId of attributed) {
231
+ const authorship = attributedAuthors.has(entityId);
232
+ credit(entityId, memory, authorship ? weight * WHO_KNOWS_AUTHORSHIP_BONUS : weight, authorship);
233
+ }
234
+ }
235
+
236
+ const ranked = [...candidates.values()].sort((a, b) => {
237
+ if (b.rawScore !== a.rawScore) return b.rawScore - a.rawScore;
238
+ const aSeen = a.lastSeen ?? "";
239
+ const bSeen = b.lastSeen ?? "";
240
+ if (aSeen !== bSeen) return aSeen < bSeen ? 1 : -1;
241
+ return a.entityId < b.entityId ? -1 : a.entityId > b.entityId ? 1 : 0;
242
+ });
243
+ const top = ranked.slice(0, input.limit);
244
+ const maxScore = top.length > 0 ? top[0].rawScore : 0;
245
+ const results: WhoKnowsHit[] = top.map((candidate) => ({
246
+ entityId: candidate.entityId,
247
+ entityName: candidate.entityName,
248
+ score: maxScore > 0 ? Math.round((candidate.rawScore / maxScore) * 10000) / 10000 : 0,
249
+ rationale:
250
+ `${candidate.matchCount} matching memor${candidate.matchCount === 1 ? "y" : "ies"}` +
251
+ ` (topic co-occurrence${candidate.authorshipCount > 0 ? `, ${candidate.authorshipCount} with direct attribution` : ""})` +
252
+ `${candidate.lastSeen ? `; latest ${candidate.lastSeen}` : ""}`,
253
+ evidenceCount: candidate.matchCount,
254
+ lastSeen: candidate.lastSeen,
255
+ evidence: candidate.evidence,
256
+ }));
257
+
258
+ return { topic: input.topic, results };
259
+ }
260
+
261
+ // ---------------------------------------------------------------------------
262
+ // MCP tool definition (spread into access-mcp.ts tools/list — see
263
+ // MEETINGS_MCP_TOOLS precedent; the type-only import keeps this cycle-free).
264
+ // ---------------------------------------------------------------------------
265
+
266
+ export const WHO_KNOWS_MCP_TOOLS: McpTool[] = [
267
+ {
268
+ // Registered as `engram.who_knows`; `withToolAliases` emits the canonical
269
+ // `remnic.who_knows` alias automatically (dual-naming invariant).
270
+ name: "engram.who_knows",
271
+ description:
272
+ "Rank people/entities by demonstrated expertise for a topic (issue #2057). Deterministic scoring over entity mentions, attribution, importance, and recency; each hit carries evidence refs (ids/paths/timestamps). Empty topic is a validation error; a topic with no evidence returns an empty list.",
273
+ inputSchema: {
274
+ type: "object",
275
+ properties: {
276
+ topic: {
277
+ type: "string",
278
+ description: "Topic to rank expertise for. Required; non-empty.",
279
+ },
280
+ limit: {
281
+ type: "integer",
282
+ minimum: 1,
283
+ maximum: WHO_KNOWS_MAX_LIMIT,
284
+ description: `Maximum hits to return (default ${WHO_KNOWS_DEFAULT_LIMIT}, max ${WHO_KNOWS_MAX_LIMIT}).`,
285
+ },
286
+ namespace: {
287
+ type: "string",
288
+ description:
289
+ "Optional namespace. Enforced against the caller's principal the same way recall is.",
290
+ },
291
+ },
292
+ required: ["topic"],
293
+ additionalProperties: false,
294
+ },
295
+ },
296
+ ];
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // HTTP query handling (called from access-http.ts so the route stays thin —
300
+ // that file is size-grandfathered).
301
+ // ---------------------------------------------------------------------------
302
+
303
+ export interface WhoKnowsHttpOutcome {
304
+ status: number;
305
+ body: unknown;
306
+ }
307
+
308
+ export async function handleWhoKnowsHttpQuery(deps: {
309
+ getParam: (name: string) => string | null;
310
+ resolveNamespace: (namespace: string | undefined) => string | undefined;
311
+ principal?: string;
312
+ run: (request: { topic: string; limit?: number; namespace?: string; authenticatedPrincipal?: string }) => Promise<WhoKnowsResult>;
313
+ }): Promise<WhoKnowsHttpOutcome> {
314
+ const topicParam = deps.getParam("topic");
315
+ if (topicParam === null || topicParam.trim().length === 0) {
316
+ return {
317
+ status: 400,
318
+ body: {
319
+ error: "missing_topic",
320
+ code: "missing_topic",
321
+ message: "topic search parameter is required and must be non-empty",
322
+ },
323
+ };
324
+ }
325
+ let limit: number | undefined;
326
+ const limitParam = deps.getParam("limit");
327
+ if (limitParam !== null && limitParam !== "") {
328
+ const parsed = Number(limitParam);
329
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > WHO_KNOWS_MAX_LIMIT) {
330
+ return {
331
+ status: 400,
332
+ body: {
333
+ error: "invalid_limit",
334
+ code: "invalid_limit",
335
+ message: `limit expects an integer between 1 and ${WHO_KNOWS_MAX_LIMIT}`,
336
+ },
337
+ };
338
+ }
339
+ limit = parsed;
340
+ }
341
+ const namespaceParam = deps.getParam("namespace");
342
+ // Always resolve (and allow-list check) the effective namespace, even when
343
+ // the param is omitted — the implicit default namespace is still a
344
+ // namespace every other GET route gates the same way.
345
+ const namespace = deps.resolveNamespace(namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined);
346
+ try {
347
+ return {
348
+ status: 200,
349
+ body: await deps.run({
350
+ topic: topicParam,
351
+ ...(limit !== undefined ? { limit } : {}),
352
+ ...(namespace !== undefined ? { namespace } : {}),
353
+ ...(deps.principal ? { authenticatedPrincipal: deps.principal } : {}),
354
+ }),
355
+ };
356
+ } catch (err) {
357
+ // Input validation (including namespace ACL) is a 400; backend faults
358
+ // bubble to the global handler so they return 500 and get logged.
359
+ if (err instanceof EngramAccessInputError) {
360
+ return { status: 400, body: { error: "invalid_request", code: "invalid_request", message: err.message } };
361
+ }
362
+ throw err;
363
+ }
364
+ }