@remnic/core 9.19.0 → 9.21.0

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 (306) hide show
  1. package/dist/access-admin-ops-surface.d.ts +8 -8
  2. package/dist/access-admin-ops-surface.js +8 -8
  3. package/dist/access-boundary.d.ts +8 -8
  4. package/dist/access-boundary.js +8 -8
  5. package/dist/access-cli.js +32 -32
  6. package/dist/access-http.d.ts +8 -8
  7. package/dist/access-http.js +12 -12
  8. package/dist/access-identity-continuity-surface.d.ts +6 -6
  9. package/dist/access-identity-continuity-surface.js +8 -8
  10. package/dist/access-lcm-surface.d.ts +8 -8
  11. package/dist/access-lcm-surface.js +8 -8
  12. package/dist/access-mcp.d.ts +8 -8
  13. package/dist/access-mcp.js +11 -11
  14. package/dist/access-memory-search-fanout.d.ts +2 -2
  15. package/dist/{access-namespace-preflight-BsawJa-L.d.ts → access-namespace-preflight-tmwQgCT6.d.ts} +1 -1
  16. package/dist/access-namespace-preflight.d.ts +3 -3
  17. package/dist/access-namespace-preflight.js +8 -8
  18. package/dist/access-observe-write-surface.d.ts +8 -8
  19. package/dist/access-observe-write-surface.js +8 -8
  20. package/dist/access-operations-batch.js +9 -9
  21. package/dist/access-operations.d.ts +8 -8
  22. package/dist/access-operations.js +10 -10
  23. package/dist/access-recall-concurrency.d.ts +8 -8
  24. package/dist/access-recall-concurrency.js +8 -8
  25. package/dist/access-recall-response.d.ts +8 -8
  26. package/dist/access-recall-response.js +8 -8
  27. package/dist/access-recall-surface.d.ts +8 -8
  28. package/dist/access-recall-surface.js +8 -8
  29. package/dist/{access-service-DLuVnV6a.d.ts → access-service-BqUii7LY.d.ts} +6 -6
  30. package/dist/access-service.d.ts +8 -8
  31. package/dist/access-service.js +8 -8
  32. package/dist/access-surface-catalog.d.ts +8 -8
  33. package/dist/action-confidence.d.ts +1 -1
  34. package/dist/active-memory-bridge.d.ts +1 -1
  35. package/dist/active-recall.d.ts +1 -1
  36. package/dist/active-recall.js +1 -1
  37. package/dist/{auto-sync-IH3G25MT.js → auto-sync-UPTUVE4X.js} +3 -3
  38. package/dist/behavior-learner.d.ts +1 -1
  39. package/dist/behavior-signals.d.ts +1 -1
  40. package/dist/bootstrap.d.ts +6 -6
  41. package/dist/briefing.d.ts +2 -2
  42. package/dist/briefing.js +4 -4
  43. package/dist/buffer-surprise-report.d.ts +1 -1
  44. package/dist/buffer.d.ts +2 -2
  45. package/dist/bulk-import/index.d.ts +3 -3
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/capabilities.d.ts +1 -1
  48. package/dist/{catalog-O6P8694A.d.ts → catalog-iWvWz61I.d.ts} +1 -1
  49. package/dist/causal-behavior.d.ts +1 -1
  50. package/dist/causal-consolidation.d.ts +1 -1
  51. package/dist/causal-consolidation.js +5 -5
  52. package/dist/causal-trajectory-graph.d.ts +1 -1
  53. package/dist/{chunk-YUWFASAK.js → chunk-275ENJDK.js} +2 -2
  54. package/dist/{chunk-EZFVVEQG.js → chunk-3ZUY2VQF.js} +2 -2
  55. package/dist/{chunk-HRREDJSL.js → chunk-4F6AANKS.js} +3 -3
  56. package/dist/{chunk-U7D5HWNG.js → chunk-4PYLBXX6.js} +2 -2
  57. package/dist/{chunk-3LZSS77C.js → chunk-53WDAG7X.js} +2 -2
  58. package/dist/{chunk-QEEMLGCO.js → chunk-5IXPWEQB.js} +5 -5
  59. package/dist/{chunk-VSVX777Y.js → chunk-5JQLAQUZ.js} +2 -2
  60. package/dist/{chunk-DR65EEXG.js → chunk-62QM5MKY.js} +7 -7
  61. package/dist/{chunk-WYL5ODHJ.js → chunk-6NZ7U4R4.js} +1 -1
  62. package/dist/chunk-6NZ7U4R4.js.map +1 -0
  63. package/dist/{chunk-OFQUY5NZ.js → chunk-7M6APKJL.js} +331 -5
  64. package/dist/chunk-7M6APKJL.js.map +1 -0
  65. package/dist/{chunk-OOJQ6ID5.js → chunk-AT3TDN6O.js} +1017 -296
  66. package/dist/chunk-AT3TDN6O.js.map +1 -0
  67. package/dist/{chunk-DQCUBRTZ.js → chunk-B7C3TF5H.js} +2 -2
  68. package/dist/{chunk-RPOOJPMB.js → chunk-EILX7SQR.js} +3 -3
  69. package/dist/{chunk-D5H3B36B.js → chunk-EXU5IQ3S.js} +2 -2
  70. package/dist/{chunk-KUF7OIFK.js → chunk-EZKKHVFY.js} +2 -2
  71. package/dist/{chunk-S5HCKQ7Q.js → chunk-GEUDRENF.js} +2 -2
  72. package/dist/{chunk-X7GOH7Y6.js → chunk-HJEEAO4O.js} +2 -2
  73. package/dist/{chunk-FL7MNSH3.js → chunk-HPOY2PHQ.js} +3 -3
  74. package/dist/{chunk-Q55XM4SU.js → chunk-JFDEYXF7.js} +2 -2
  75. package/dist/{chunk-UVSDFM7R.js → chunk-JN3IUA2Q.js} +2 -2
  76. package/dist/{chunk-PLRDZ2OO.js → chunk-JWMYVQAM.js} +2 -2
  77. package/dist/{chunk-KOFVOVJG.js → chunk-KHRKEELC.js} +17 -17
  78. package/dist/{chunk-Y4JHE2ML.js → chunk-LI4ZOFHW.js} +2 -2
  79. package/dist/{chunk-MLW3J7IC.js → chunk-LMHDFOVV.js} +3 -3
  80. package/dist/{chunk-X6E5WAGX.js → chunk-N7EVMKP6.js} +6 -6
  81. package/dist/{chunk-HCNG42P3.js → chunk-Q3FWRDCP.js} +2 -2
  82. package/dist/{chunk-5MFHOX5T.js → chunk-QO2KODU6.js} +2 -2
  83. package/dist/{chunk-W3DJOT22.js → chunk-TCZWBJBO.js} +2 -2
  84. package/dist/{chunk-B3ABVQ36.js → chunk-UHVXE24Y.js} +2 -1
  85. package/dist/{chunk-UX3ZXN34.js → chunk-UV72DEZD.js} +2 -2
  86. package/dist/{chunk-OZSL2QZC.js → chunk-V5TSUGRB.js} +5 -5
  87. package/dist/{chunk-CH3BRJ3P.js → chunk-X5QS5C2O.js} +2 -2
  88. package/dist/{cli-0dQHR6e6.d.ts → cli-DPEhq0Cp.d.ts} +5 -5
  89. package/dist/cli.d.ts +10 -10
  90. package/dist/cli.js +25 -25
  91. package/dist/compounding/engine.d.ts +2 -2
  92. package/dist/compounding/engine.js +4 -4
  93. package/dist/compounding/preference-consolidator.d.ts +1 -1
  94. package/dist/compression-optimizer.d.ts +1 -1
  95. package/dist/config.d.ts +1 -1
  96. package/dist/config.js +1 -1
  97. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  98. package/dist/connectors/codex-materialize-runner.js +4 -4
  99. package/dist/connectors/codex-materialize.d.ts +1 -1
  100. package/dist/connectors/index.d.ts +1 -1
  101. package/dist/connectors/index.js +4 -4
  102. package/dist/consolidation-provenance-check.d.ts +2 -2
  103. package/dist/consolidation-undo.d.ts +2 -2
  104. package/dist/contradiction/index.d.ts +2 -2
  105. package/dist/conversation-index/backend.d.ts +1 -1
  106. package/dist/conversation-index/chunker.d.ts +1 -1
  107. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  108. package/dist/conversation-index/indexer.d.ts +1 -1
  109. package/dist/conversation-index/search.d.ts +1 -1
  110. package/dist/day-summary.d.ts +1 -1
  111. package/dist/delinearize.d.ts +1 -1
  112. package/dist/direct-answer-wiring.d.ts +1 -1
  113. package/dist/direct-answer.d.ts +1 -1
  114. package/dist/embedding-fallback.d.ts +1 -1
  115. package/dist/enrichment/index.d.ts +1 -1
  116. package/dist/entity-retrieval.d.ts +2 -2
  117. package/dist/entity-retrieval.js +4 -4
  118. package/dist/entity-schema.d.ts +1 -1
  119. package/dist/explicit-capture.d.ts +6 -6
  120. package/dist/extraction-faithfulness.d.ts +1 -1
  121. package/dist/extraction-judge-telemetry.d.ts +1 -1
  122. package/dist/extraction-judge-training.d.ts +1 -1
  123. package/dist/extraction-judge.d.ts +1 -1
  124. package/dist/extraction.d.ts +1 -1
  125. package/dist/fallback-llm.d.ts +1 -1
  126. package/dist/graph-dashboard-diff.d.ts +1 -1
  127. package/dist/graph-dashboard-key.d.ts +1 -1
  128. package/dist/graph-dashboard-parser.d.ts +1 -1
  129. package/dist/graph-edge-reinforcement.d.ts +1 -1
  130. package/dist/graph-snapshot.d.ts +1 -1
  131. package/dist/graph.d.ts +1 -1
  132. package/dist/importance.d.ts +1 -1
  133. package/dist/importers/index.d.ts +1 -1
  134. package/dist/in-flight-reads.d.ts +1 -1
  135. package/dist/index.d.ts +468 -88
  136. package/dist/index.js +66 -36
  137. package/dist/intent.d.ts +1 -1
  138. package/dist/lcm/engine.d.ts +1 -1
  139. package/dist/lcm/index.d.ts +1 -1
  140. package/dist/lcm/tools.d.ts +1 -1
  141. package/dist/lifecycle.d.ts +1 -1
  142. package/dist/live-connectors-runner.d.ts +1 -1
  143. package/dist/local-llm.d.ts +1 -1
  144. package/dist/local-model-endpoint.d.ts +1 -1
  145. package/dist/maintenance/memory-governance.d.ts +1 -1
  146. package/dist/maintenance/memory-governance.js +4 -4
  147. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  148. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  149. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  150. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  151. package/dist/mcp-memory-inspector-app.d.ts +8 -8
  152. package/dist/memory-action-policy.d.ts +1 -1
  153. package/dist/memory-cache.d.ts +1 -1
  154. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  155. package/dist/memory-projection-store.d.ts +1 -1
  156. package/dist/memory-provenance.d.ts +1 -1
  157. package/dist/memory-worth-outcomes.d.ts +2 -2
  158. package/dist/models-json.d.ts +1 -1
  159. package/dist/namespaces/migrate.d.ts +3 -3
  160. package/dist/namespaces/migrate.js +5 -5
  161. package/dist/namespaces/principal.d.ts +1 -1
  162. package/dist/namespaces/search.d.ts +1 -1
  163. package/dist/namespaces/storage.d.ts +3 -3
  164. package/dist/namespaces/storage.js +4 -4
  165. package/dist/native-knowledge.d.ts +1 -1
  166. package/dist/offline-sync-impression-drain.d.ts +1 -1
  167. package/dist/operator-toolkit.d.ts +2 -2
  168. package/dist/operator-toolkit.js +9 -9
  169. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  170. package/dist/orchestration/maintenance.d.ts +3 -3
  171. package/dist/orchestration/maintenance.js +9 -9
  172. package/dist/{orchestrator-B8ickLkd.d.ts → orchestrator-DB93S48I.d.ts} +5 -5
  173. package/dist/orchestrator.d.ts +6 -6
  174. package/dist/orchestrator.js +32 -32
  175. package/dist/patterns-cli.d.ts +1 -1
  176. package/dist/{pipeline-B669unGm.d.ts → pipeline-DmGc-H66.d.ts} +1 -1
  177. package/dist/policy-runtime.d.ts +1 -1
  178. package/dist/proactive-contention.d.ts +1 -1
  179. package/dist/provenance.d.ts +1 -1
  180. package/dist/{qmd-D0PVXmea.d.ts → qmd-cJT0crkF.d.ts} +1 -1
  181. package/dist/qmd-preflight.d.ts +2 -2
  182. package/dist/qmd-recall-cache.d.ts +1 -1
  183. package/dist/qmd.d.ts +2 -2
  184. package/dist/recall-concurrency-config.d.ts +1 -1
  185. package/dist/recall-disclosure-escalation.d.ts +1 -1
  186. package/dist/recall-explain-renderer.d.ts +1 -1
  187. package/dist/recall-explain-renderer.js +3 -3
  188. package/dist/recall-memory-map.d.ts +1 -1
  189. package/dist/recall-planner-llm.d.ts +1 -1
  190. package/dist/recall-state.d.ts +1 -1
  191. package/dist/recall-tag-filter.d.ts +1 -1
  192. package/dist/recall-timings.d.ts +1 -1
  193. package/dist/recall-xray-cli.d.ts +1 -1
  194. package/dist/recall-xray-cli.js +4 -4
  195. package/dist/recall-xray-renderer.d.ts +1 -1
  196. package/dist/recall-xray-renderer.js +3 -3
  197. package/dist/recall-xray.d.ts +1 -1
  198. package/dist/recall-xray.js +2 -2
  199. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  200. package/dist/replay/normalizers/claude.d.ts +1 -1
  201. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  202. package/dist/replay/normalizers/shared.d.ts +1 -1
  203. package/dist/replay/runner.d.ts +1 -1
  204. package/dist/replay/types.d.ts +1 -1
  205. package/dist/resolve-auth-token.d.ts +1 -1
  206. package/dist/resume-bundles.js +2 -2
  207. package/dist/retrieval-agents.d.ts +2 -2
  208. package/dist/retrieval-tiers.d.ts +1 -1
  209. package/dist/routing/engine.d.ts +1 -1
  210. package/dist/routing/store.d.ts +1 -1
  211. package/dist/salvage-envelope.d.ts +1 -1
  212. package/dist/{scope-profiles-BOp3gSzp.d.ts → scope-profiles-KG2nODX7.d.ts} +1 -1
  213. package/dist/search/embed-helper.d.ts +1 -1
  214. package/dist/search/factory.d.ts +1 -1
  215. package/dist/search/index.d.ts +1 -1
  216. package/dist/search/lancedb-backend.d.ts +1 -1
  217. package/dist/search/meilisearch-backend.d.ts +1 -1
  218. package/dist/search/noop-backend.d.ts +1 -1
  219. package/dist/search/orama-backend.d.ts +1 -1
  220. package/dist/search/port.d.ts +1 -1
  221. package/dist/search/remote-backend.d.ts +1 -1
  222. package/dist/{semantic-consolidation-C7SZNzUJ.d.ts → semantic-consolidation-BaZlg0D8.d.ts} +1 -1
  223. package/dist/semantic-consolidation.d.ts +2 -2
  224. package/dist/semantic-consolidation.js +5 -5
  225. package/dist/semantic-rule-promotion.js +4 -4
  226. package/dist/semantic-rule-verifier.d.ts +1 -1
  227. package/dist/semantic-rule-verifier.js +4 -4
  228. package/dist/session-observer-bands.d.ts +1 -1
  229. package/dist/session-observer-state.d.ts +1 -1
  230. package/dist/shared-context/manager.d.ts +1 -1
  231. package/dist/signal.d.ts +1 -1
  232. package/dist/{storage-CJ_Fia4H.d.ts → storage-BKhHHnRo.d.ts} +2 -221
  233. package/dist/storage.d.ts +2 -2
  234. package/dist/storage.js +3 -3
  235. package/dist/summarizer.d.ts +1 -1
  236. package/dist/summary-snapshot.d.ts +1 -1
  237. package/dist/temporal-supersession.d.ts +2 -2
  238. package/dist/temporal-timeline-recall.d.ts +1 -1
  239. package/dist/temporal-validity.d.ts +1 -1
  240. package/dist/threading.d.ts +1 -1
  241. package/dist/tier-migration.d.ts +2 -2
  242. package/dist/tier-routing.d.ts +1 -1
  243. package/dist/topics.d.ts +1 -1
  244. package/dist/transcript.d.ts +1 -1
  245. package/dist/trust-score-stage.d.ts +1 -1
  246. package/dist/trust-score.d.ts +1 -1
  247. package/dist/{types-GeWGXq9E.d.ts → types-GBQZuT7l.d.ts} +395 -1
  248. package/dist/types.d.ts +1 -1
  249. package/dist/types.js +1 -1
  250. package/dist/utility-runtime.d.ts +1 -1
  251. package/dist/verified-recall.js +4 -4
  252. package/dist/write-envelope.d.ts +1 -1
  253. package/package.json +2 -2
  254. package/src/config.ts +3 -3
  255. package/src/meetings/build.test.ts +646 -0
  256. package/src/meetings/build.ts +365 -0
  257. package/src/meetings/cli.test.ts +276 -0
  258. package/src/meetings/cli.ts +242 -0
  259. package/src/meetings/config.test.ts +72 -0
  260. package/src/meetings/config.ts +183 -0
  261. package/src/meetings/errors.ts +13 -0
  262. package/src/meetings/fuse.test.ts +208 -0
  263. package/src/meetings/fuse.ts +297 -0
  264. package/src/meetings/index.ts +11 -1
  265. package/src/meetings/memory-generator.ts +116 -0
  266. package/src/meetings/recall-exclusion.test.ts +88 -0
  267. package/src/meetings/store.test.ts +232 -0
  268. package/src/meetings/store.ts +465 -0
  269. package/src/meetings/types.ts +111 -0
  270. package/src/orchestration/orchestrator-helpers.ts +23 -3
  271. package/src/orchestration/recall-search-pipeline.test.ts +182 -0
  272. package/src/orchestration/recall-search-pipeline.ts +36 -15
  273. package/src/types.ts +6 -0
  274. package/dist/chunk-OFQUY5NZ.js.map +0 -1
  275. package/dist/chunk-OOJQ6ID5.js.map +0 -1
  276. package/dist/chunk-WYL5ODHJ.js.map +0 -1
  277. /package/dist/{auto-sync-IH3G25MT.js.map → auto-sync-UPTUVE4X.js.map} +0 -0
  278. /package/dist/{chunk-YUWFASAK.js.map → chunk-275ENJDK.js.map} +0 -0
  279. /package/dist/{chunk-EZFVVEQG.js.map → chunk-3ZUY2VQF.js.map} +0 -0
  280. /package/dist/{chunk-HRREDJSL.js.map → chunk-4F6AANKS.js.map} +0 -0
  281. /package/dist/{chunk-U7D5HWNG.js.map → chunk-4PYLBXX6.js.map} +0 -0
  282. /package/dist/{chunk-3LZSS77C.js.map → chunk-53WDAG7X.js.map} +0 -0
  283. /package/dist/{chunk-QEEMLGCO.js.map → chunk-5IXPWEQB.js.map} +0 -0
  284. /package/dist/{chunk-VSVX777Y.js.map → chunk-5JQLAQUZ.js.map} +0 -0
  285. /package/dist/{chunk-DR65EEXG.js.map → chunk-62QM5MKY.js.map} +0 -0
  286. /package/dist/{chunk-DQCUBRTZ.js.map → chunk-B7C3TF5H.js.map} +0 -0
  287. /package/dist/{chunk-RPOOJPMB.js.map → chunk-EILX7SQR.js.map} +0 -0
  288. /package/dist/{chunk-D5H3B36B.js.map → chunk-EXU5IQ3S.js.map} +0 -0
  289. /package/dist/{chunk-KUF7OIFK.js.map → chunk-EZKKHVFY.js.map} +0 -0
  290. /package/dist/{chunk-S5HCKQ7Q.js.map → chunk-GEUDRENF.js.map} +0 -0
  291. /package/dist/{chunk-X7GOH7Y6.js.map → chunk-HJEEAO4O.js.map} +0 -0
  292. /package/dist/{chunk-FL7MNSH3.js.map → chunk-HPOY2PHQ.js.map} +0 -0
  293. /package/dist/{chunk-Q55XM4SU.js.map → chunk-JFDEYXF7.js.map} +0 -0
  294. /package/dist/{chunk-UVSDFM7R.js.map → chunk-JN3IUA2Q.js.map} +0 -0
  295. /package/dist/{chunk-PLRDZ2OO.js.map → chunk-JWMYVQAM.js.map} +0 -0
  296. /package/dist/{chunk-KOFVOVJG.js.map → chunk-KHRKEELC.js.map} +0 -0
  297. /package/dist/{chunk-Y4JHE2ML.js.map → chunk-LI4ZOFHW.js.map} +0 -0
  298. /package/dist/{chunk-MLW3J7IC.js.map → chunk-LMHDFOVV.js.map} +0 -0
  299. /package/dist/{chunk-X6E5WAGX.js.map → chunk-N7EVMKP6.js.map} +0 -0
  300. /package/dist/{chunk-HCNG42P3.js.map → chunk-Q3FWRDCP.js.map} +0 -0
  301. /package/dist/{chunk-5MFHOX5T.js.map → chunk-QO2KODU6.js.map} +0 -0
  302. /package/dist/{chunk-W3DJOT22.js.map → chunk-TCZWBJBO.js.map} +0 -0
  303. /package/dist/{chunk-B3ABVQ36.js.map → chunk-UHVXE24Y.js.map} +0 -0
  304. /package/dist/{chunk-UX3ZXN34.js.map → chunk-UV72DEZD.js.map} +0 -0
  305. /package/dist/{chunk-OZSL2QZC.js.map → chunk-V5TSUGRB.js.map} +0 -0
  306. /package/dist/{chunk-CH3BRJ3P.js.map → chunk-X5QS5C2O.js.map} +0 -0
@@ -0,0 +1,88 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ filterRecallCandidates,
6
+ isArtifactMemoryPath,
7
+ isMeetingRecordPath,
8
+ isGenericRecallExcludedPath,
9
+ } from "../orchestration/orchestrator-helpers.js";
10
+ import type { QmdSearchResult } from "../types.js";
11
+
12
+ // Regression for issue #1900: meeting records live inside the QMD collection
13
+ // root (full-text searchable) but must NEVER be surfaced through generic recall.
14
+ // They ride the SAME retrieval-policy predicate as artifacts, not a parallel
15
+ // filter, so `filterRecallCandidates` (the shared recall chokepoint) drops them.
16
+
17
+ function result(path: string): QmdSearchResult {
18
+ return { docid: path, path, snippet: "", score: 1 };
19
+ }
20
+
21
+ test("isMeetingRecordPath matches only the full meeting-record shape", () => {
22
+ assert.equal(isMeetingRecordPath("meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"), true);
23
+ assert.equal(isMeetingRecordPath("/mem/meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"), true);
24
+ assert.equal(isMeetingRecordPath("namespaces/team/meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"), true);
25
+ // A namespace literally named "meetings" must NOT lose its ordinary memories.
26
+ assert.equal(isMeetingRecordPath("namespaces/meetings/facts/a.md"), false);
27
+ assert.equal(isMeetingRecordPath("namespaces/meetings/preferences/p.md"), false);
28
+ // A non-record file under a meetings day dir is not a record.
29
+ assert.equal(isMeetingRecordPath("meetings/2026-03-10/notes.md"), false);
30
+ assert.equal(isMeetingRecordPath("facts/a.md"), false);
31
+ assert.equal(isMeetingRecordPath("facts/team-meetings-notes.md"), false);
32
+ });
33
+
34
+ test("isGenericRecallExcludedPath unifies artifacts + meeting records", () => {
35
+ assert.equal(isGenericRecallExcludedPath("artifacts/x.md"), true);
36
+ assert.equal(isGenericRecallExcludedPath("meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"), true);
37
+ assert.equal(isGenericRecallExcludedPath("facts/a.md"), false);
38
+ // A namespace named "meetings" is not a record dir → its memories stay recallable.
39
+ assert.equal(isGenericRecallExcludedPath("namespaces/meetings/facts/a.md"), false);
40
+ // The artifact predicate itself is unchanged.
41
+ assert.equal(isArtifactMemoryPath("meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"), false);
42
+ });
43
+
44
+ test("filterRecallCandidates drops meeting records alongside artifacts", () => {
45
+ const candidates = [
46
+ result("facts/a.md"),
47
+ result("meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"),
48
+ result("artifacts/b.md"),
49
+ result("decisions/c.md"),
50
+ ];
51
+ const kept = filterRecallCandidates(candidates, {
52
+ namespacesEnabled: false,
53
+ recallNamespaces: [],
54
+ resolveNamespace: () => "",
55
+ limit: 10,
56
+ });
57
+ assert.deepEqual(
58
+ kept.map((r) => r.path),
59
+ ["facts/a.md", "decisions/c.md"],
60
+ );
61
+ });
62
+
63
+ test("filterRecallCandidates drops non-recallable paths BEFORE applying the limit", () => {
64
+ // A meeting record sits within the first `limit` positions. Filter-before-cap
65
+ // must still yield `limit` RECALLABLE results; a cap-before-filter bug would
66
+ // spend a slot on the dropped record and return fewer.
67
+ const candidates = [
68
+ result("meetings/2026-03-10/mtg-2026-03-10-abcdef01.md"),
69
+ result("facts/a.md"),
70
+ result("facts/b.md"),
71
+ result("facts/c.md"),
72
+ ];
73
+ const kept = filterRecallCandidates(candidates, {
74
+ namespacesEnabled: false,
75
+ recallNamespaces: [],
76
+ resolveNamespace: () => "",
77
+ limit: 2,
78
+ });
79
+ assert.equal(kept.length, 2, "the cap must be filled with recallable results, not consumed by the dropped record");
80
+ assert.deepEqual(
81
+ kept.map((r) => r.path),
82
+ ["facts/a.md", "facts/b.md"],
83
+ );
84
+ assert.ok(
85
+ kept.every((r) => !isGenericRecallExcludedPath(r.path)),
86
+ "no non-recallable path may survive the cap",
87
+ );
88
+ });
@@ -0,0 +1,232 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ composeMeetingRecord,
6
+ MeetingRecordStore,
7
+ parseMeetingRecordSummary,
8
+ serializeMeetingRecord,
9
+ type MeetingRecordBase,
10
+ type MeetingRecordFileIo,
11
+ } from "./store.js";
12
+ import type { FusedMeeting } from "./types.js";
13
+ import type { FusedSegment, FusedSpeaker } from "../wearables/fusion/types.js";
14
+
15
+ const MEMORY_DIR = "/mem";
16
+ const DATE = "2026-03-10";
17
+
18
+ /** In-memory secure IO for tests: no real filesystem. */
19
+ class InMemoryIo implements MeetingRecordFileIo {
20
+ files = new Map<string, string>();
21
+ symlinkedDirs = new Set<string>();
22
+
23
+ async writeFile(filePath: string, content: string): Promise<void> {
24
+ this.files.set(filePath, content);
25
+ }
26
+ async readFile(filePath: string): Promise<string> {
27
+ const value = this.files.get(filePath);
28
+ if (value === undefined) throw enoent();
29
+ return value;
30
+ }
31
+ async readDir(dirPath: string): Promise<string[]> {
32
+ const prefix = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
33
+ const names = new Set<string>();
34
+ let found = false;
35
+ for (const key of this.files.keys()) {
36
+ if (!key.startsWith(prefix)) continue;
37
+ found = true;
38
+ names.add(key.slice(prefix.length).split("/")[0]!);
39
+ }
40
+ if (!found) throw enoent();
41
+ return [...names];
42
+ }
43
+ async deleteFile(filePath: string): Promise<void> {
44
+ if (!this.files.delete(filePath)) throw enoent();
45
+ }
46
+ async realpath(filePath: string): Promise<string> {
47
+ return filePath;
48
+ }
49
+ async lstat(filePath: string): Promise<{ isSymbolicLink: boolean }> {
50
+ return { isSymbolicLink: this.symlinkedDirs.has(filePath) };
51
+ }
52
+ }
53
+
54
+ function enoent(): NodeJS.ErrnoException {
55
+ const err = new Error("ENOENT") as NodeJS.ErrnoException;
56
+ err.code = "ENOENT";
57
+ return err;
58
+ }
59
+
60
+ function speaker(label: string, isSelf: boolean): FusedSpeaker {
61
+ return { label, isSelf, confidence: 1, sources: ["desktop"] };
62
+ }
63
+
64
+ function segment(speakerLabel: string, text: string, startIso: string): FusedSegment {
65
+ return {
66
+ speaker: speakerLabel,
67
+ isSelf: false,
68
+ text,
69
+ startIso,
70
+ confidence: 0.9,
71
+ provenance: { source: "desktop", conversationId: "d1", sourceTrust: 0.9, reason: "only-source", alternatives: [] },
72
+ };
73
+ }
74
+
75
+ function fused(overrides: Partial<FusedMeeting> = {}): FusedMeeting {
76
+ return {
77
+ attendees: ["Jane"],
78
+ sources: ["desktop"],
79
+ corroboratedBy: [],
80
+ screenContext: [],
81
+ contextExcerpts: [],
82
+ transcript: [segment("Jane", "hello everyone", "2026-03-10T14:05:00.000Z")],
83
+ speakers: [speaker("Jane", false), speaker("Me (you)", true)],
84
+ snapshotCount: 0,
85
+ ...overrides,
86
+ };
87
+ }
88
+
89
+ function base(overrides: Partial<MeetingRecordBase> = {}): MeetingRecordBase {
90
+ return {
91
+ id: "mtg-2026-03-10-abcdef01",
92
+ date: DATE,
93
+ startUtc: "2026-03-10T14:00:00.000Z",
94
+ endUtc: "2026-03-10T14:30:00.000Z",
95
+ app: "Zoom",
96
+ detectionSource: "app+audio",
97
+ ...overrides,
98
+ };
99
+ }
100
+
101
+ test("content hash rewrite is idempotent — unchanged records skip the write", async () => {
102
+ const io = new InMemoryIo();
103
+ const store = new MeetingRecordStore(MEMORY_DIR, io);
104
+ const record = composeMeetingRecord(base(), fused());
105
+
106
+ const first = await store.saveMeetingRecord(record);
107
+ assert.equal(first.written, true);
108
+ const second = await store.saveMeetingRecord(record);
109
+ assert.equal(second.written, false, "identical record must not rewrite");
110
+ assert.equal(second.contentHash, first.contentHash);
111
+
112
+ // A semantic change flips the hash and forces a rewrite.
113
+ const changed = composeMeetingRecord(base(), fused({ attendees: ["Jane", "Sam"] }));
114
+ assert.notEqual(changed.contentHash, record.contentHash);
115
+ const third = await store.saveMeetingRecord(changed);
116
+ assert.equal(third.written, true);
117
+ });
118
+
119
+ test("serialize → parse round-trips the frontmatter summary", () => {
120
+ const record = composeMeetingRecord(base(), fused({ sources: ["desktop", "pendant"], corroboratedBy: ["pendant"] }));
121
+ const raw = serializeMeetingRecord(record);
122
+ const summary = parseMeetingRecordSummary(raw);
123
+ assert.notEqual(summary, null);
124
+ assert.equal(summary?.id, record.id);
125
+ assert.equal(summary?.date, DATE);
126
+ assert.equal(summary?.contentHash, record.contentHash);
127
+ assert.equal(summary?.detectionSource, "app+audio");
128
+ assert.deepEqual(summary?.attendees, ["Jane"]);
129
+ assert.deepEqual(summary?.sources, ["desktop", "pendant"]);
130
+ assert.deepEqual(summary?.corroboratedBy, ["pendant"]);
131
+ assert.equal(summary?.app, "Zoom");
132
+ });
133
+
134
+ test("serialized body carries the day-store transcript line grammar", () => {
135
+ const raw = serializeMeetingRecord(composeMeetingRecord(base(), fused()));
136
+ assert.match(raw, /\*\*Jane\*\* \[14:05\]: hello everyone/);
137
+ assert.match(raw, /## Transcript/);
138
+ assert.match(raw, /## Attendees/);
139
+ });
140
+
141
+ test("parseMeetingRecordSummary rejects non-meeting content", () => {
142
+ assert.equal(parseMeetingRecordSummary("not frontmatter"), null);
143
+ assert.equal(parseMeetingRecordSummary("---\nkind: wearable-fusion\n---\n"), null);
144
+ });
145
+
146
+ test("recordPath validates date, id, and their agreement", () => {
147
+ const store = new MeetingRecordStore(MEMORY_DIR, new InMemoryIo());
148
+ assert.throws(() => store.recordPath("2026-13-40", "mtg-2026-03-10-abcdef01"), /invalid meeting date/);
149
+ assert.throws(() => store.recordPath(DATE, "not-a-meeting-id"), /invalid meeting id/);
150
+ assert.throws(() => store.recordPath(DATE, "mtg-2026-03-11-abcdef01"), /does not belong to date/);
151
+ });
152
+
153
+ test("listing returns days newest-first and ids/summaries by start time", async () => {
154
+ const io = new InMemoryIo();
155
+ const store = new MeetingRecordStore(MEMORY_DIR, io);
156
+ const early = composeMeetingRecord(
157
+ base({ id: "mtg-2026-03-10-00000001", startUtc: "2026-03-10T09:00:00.000Z" }),
158
+ fused(),
159
+ );
160
+ const late = composeMeetingRecord(
161
+ base({ id: "mtg-2026-03-10-00000002", startUtc: "2026-03-10T15:00:00.000Z" }),
162
+ fused(),
163
+ );
164
+ const otherDay = composeMeetingRecord(
165
+ base({ id: "mtg-2026-03-11-00000003", date: "2026-03-11", startUtc: "2026-03-11T10:00:00.000Z" }),
166
+ fused(),
167
+ );
168
+ // Insert late before early to prove the sort is by start, not insertion.
169
+ await store.saveMeetingRecord(late);
170
+ await store.saveMeetingRecord(early);
171
+ await store.saveMeetingRecord(otherDay);
172
+
173
+ assert.deepEqual(await store.listMeetingDates(), ["2026-03-11", "2026-03-10"]);
174
+ assert.deepEqual(await store.listMeetingIds(DATE), [
175
+ "mtg-2026-03-10-00000001",
176
+ "mtg-2026-03-10-00000002",
177
+ ]);
178
+ const summaries = await store.listMeetingSummaries(DATE);
179
+ assert.deepEqual(summaries.map((s) => s.startUtc), [
180
+ "2026-03-10T09:00:00.000Z",
181
+ "2026-03-10T15:00:00.000Z",
182
+ ]);
183
+ });
184
+
185
+ test("empty store lists nothing", async () => {
186
+ const store = new MeetingRecordStore(MEMORY_DIR, new InMemoryIo());
187
+ assert.deepEqual(await store.listMeetingDates(), []);
188
+ assert.deepEqual(await store.listMeetingIds(DATE), []);
189
+ assert.equal(await store.readMeetingRecord(DATE, "mtg-2026-03-10-abcdef01"), null);
190
+ });
191
+
192
+ test("a symlinked meetings dir is refused before any IO", async () => {
193
+ const io = new InMemoryIo();
194
+ io.symlinkedDirs.add("/mem/meetings");
195
+ const store = new MeetingRecordStore(MEMORY_DIR, io);
196
+ await assert.rejects(
197
+ () => store.saveMeetingRecord(composeMeetingRecord(base(), fused())),
198
+ /symbolic link/,
199
+ );
200
+ });
201
+
202
+ test("a symlinked intermediate day directory is refused for reads and writes", async () => {
203
+ const io = new InMemoryIo();
204
+ io.symlinkedDirs.add("/mem/meetings/2026-03-10");
205
+ const store = new MeetingRecordStore(MEMORY_DIR, io);
206
+ await assert.rejects(
207
+ () => store.saveMeetingRecord(composeMeetingRecord(base(), fused())),
208
+ /symbolic link/,
209
+ );
210
+ await assert.rejects(() => store.listMeetingIds(DATE), /symbolic link/);
211
+ await assert.rejects(
212
+ () => store.readMeetingRecord(DATE, "mtg-2026-03-10-abcdef01"),
213
+ /symbolic link/,
214
+ );
215
+ });
216
+
217
+ test("finding 4 — a symlinked leaf record file is refused for reads and writes", async () => {
218
+ const io = new InMemoryIo();
219
+ // A pre-existing `<id>.md` symlink (e.g. pointing into facts/) resolves inside
220
+ // the memory dir, so realpath containment + the dir checks pass — but a
221
+ // follow would write raw transcript into the recall corpus. Refuse the leaf.
222
+ io.symlinkedDirs.add("/mem/meetings/2026-03-10/mtg-2026-03-10-abcdef01.md");
223
+ const store = new MeetingRecordStore(MEMORY_DIR, io);
224
+ await assert.rejects(
225
+ () => store.saveMeetingRecord(composeMeetingRecord(base(), fused())),
226
+ /symbolic link/,
227
+ );
228
+ await assert.rejects(
229
+ () => store.readMeetingRecord(DATE, "mtg-2026-03-10-abcdef01"),
230
+ /symbolic link/,
231
+ );
232
+ });