@remnic/core 9.22.0 → 9.22.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 (402) hide show
  1. package/dist/access-admin-ops-surface.d.ts +15 -12
  2. package/dist/access-admin-ops-surface.js +25 -24
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-boundary.d.ts +13 -10
  5. package/dist/access-boundary.js +25 -24
  6. package/dist/access-cli.js +84 -82
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-http.d.ts +16 -9
  9. package/dist/access-http.js +33 -32
  10. package/dist/access-identity-continuity-surface.d.ts +9 -7
  11. package/dist/access-identity-continuity-surface.js +25 -24
  12. package/dist/access-lcm-surface.d.ts +12 -9
  13. package/dist/access-lcm-surface.js +25 -24
  14. package/dist/access-mcp-output-schemas.js +1 -1
  15. package/dist/access-mcp.d.ts +22 -10
  16. package/dist/access-mcp.js +31 -30
  17. package/dist/access-memory-search-fanout.d.ts +2 -2
  18. package/dist/{access-namespace-preflight-tmwQgCT6.d.ts → access-namespace-preflight-CmErR03h.d.ts} +1 -1
  19. package/dist/access-namespace-preflight.d.ts +3 -3
  20. package/dist/access-namespace-preflight.js +25 -24
  21. package/dist/access-observe-write-surface.d.ts +12 -9
  22. package/dist/access-observe-write-surface.js +25 -24
  23. package/dist/access-operations-batch.js +27 -26
  24. package/dist/access-operations.d.ts +16 -13
  25. package/dist/access-operations.js +28 -27
  26. package/dist/access-recall-concurrency.d.ts +12 -9
  27. package/dist/access-recall-concurrency.js +25 -24
  28. package/dist/access-recall-response.d.ts +12 -9
  29. package/dist/access-recall-response.js +25 -24
  30. package/dist/access-recall-surface.d.ts +14 -11
  31. package/dist/access-recall-surface.js +25 -24
  32. package/dist/access-schema.d.ts +76 -76
  33. package/dist/{access-service-BtxW52vv.d.ts → access-service-CKEROeFP.d.ts} +22 -11
  34. package/dist/access-service.d.ts +13 -10
  35. package/dist/access-service.js +25 -24
  36. package/dist/access-surface-catalog.d.ts +12 -9
  37. package/dist/access-surface-catalog.js +6 -0
  38. package/dist/access-surface-catalog.js.map +1 -1
  39. package/dist/access-wearables-meetings-surface.d.ts +78 -0
  40. package/dist/access-wearables-meetings-surface.js +22 -0
  41. package/dist/access-wearables-meetings-surface.js.map +1 -0
  42. package/dist/action-confidence.d.ts +1 -1
  43. package/dist/active-memory-bridge.d.ts +1 -1
  44. package/dist/active-recall.d.ts +1 -1
  45. package/dist/active-recall.js +2 -2
  46. package/dist/{auto-sync-UPTUVE4X.js → auto-sync-5OE5QOZB.js} +3 -3
  47. package/dist/behavior-learner.d.ts +1 -1
  48. package/dist/behavior-signals.d.ts +1 -1
  49. package/dist/bootstrap.d.ts +9 -7
  50. package/dist/briefing.d.ts +2 -2
  51. package/dist/briefing.js +5 -5
  52. package/dist/buffer-surprise-report.d.ts +1 -1
  53. package/dist/buffer.d.ts +2 -2
  54. package/dist/bulk-import/index.d.ts +3 -3
  55. package/dist/calibration.d.ts +1 -1
  56. package/dist/capabilities.d.ts +1 -1
  57. package/dist/{catalog-iWvWz61I.d.ts → catalog-BFTVo2Ez.d.ts} +1 -1
  58. package/dist/causal-behavior.d.ts +1 -1
  59. package/dist/causal-behavior.js +2 -2
  60. package/dist/causal-chain.js +2 -2
  61. package/dist/causal-consolidation.d.ts +1 -1
  62. package/dist/causal-consolidation.js +9 -9
  63. package/dist/causal-retrieval.js +2 -2
  64. package/dist/causal-trajectory-graph.d.ts +1 -1
  65. package/dist/causal-trajectory.js +1 -1
  66. package/dist/{chunk-GF22HWS2.js → chunk-2PHX3I5C.js} +7 -7
  67. package/dist/{chunk-JFDEYXF7.js → chunk-3NAHZQU3.js} +2 -2
  68. package/dist/{chunk-XBRPEGXF.js → chunk-3UOSVTFR.js} +5 -5
  69. package/dist/{chunk-TTKWTT72.js → chunk-3ZW6ZQO6.js} +3 -3
  70. package/dist/{chunk-UV72DEZD.js → chunk-4B77XSG5.js} +5 -5
  71. package/dist/{chunk-IC2TCZJ2.js → chunk-57QKLRBV.js} +3 -1
  72. package/dist/chunk-57QKLRBV.js.map +1 -0
  73. package/dist/{chunk-LMHDFOVV.js → chunk-5CPI2QWG.js} +3 -3
  74. package/dist/{chunk-P72WZMWQ.js → chunk-5FZQ74K5.js} +2 -2
  75. package/dist/{chunk-BRT7KEXU.js → chunk-6TWXQ4E2.js} +5 -5
  76. package/dist/{chunk-CV6ZYXK3.js → chunk-7I7AYIUQ.js} +3 -3
  77. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  78. package/dist/{chunk-MONWCSVL.js → chunk-7RAJ3GXO.js} +2 -1
  79. package/dist/chunk-7RAJ3GXO.js.map +1 -0
  80. package/dist/{chunk-SQEDDWAM.js → chunk-A2RFKL3V.js} +5 -34
  81. package/dist/chunk-A2RFKL3V.js.map +1 -0
  82. package/dist/{chunk-JN57HZCD.js → chunk-ATEAL5EI.js} +6 -6
  83. package/dist/{chunk-4PYLBXX6.js → chunk-AYAROCWD.js} +2 -2
  84. package/dist/chunk-BLDBT6HZ.js +39 -0
  85. package/dist/chunk-BLDBT6HZ.js.map +1 -0
  86. package/dist/{chunk-NB2P47N7.js → chunk-CUUY5CC4.js} +6 -6
  87. package/dist/{chunk-KL5Y6PEV.js → chunk-E3ZMFFQZ.js} +2 -2
  88. package/dist/{chunk-55FDQYPS.js → chunk-ES5IHQ6M.js} +2 -2
  89. package/dist/{chunk-N47ZWQBO.js → chunk-G2NSSQES.js} +2 -2
  90. package/dist/{chunk-GSWPU5CR.js → chunk-GD3QOBMB.js} +2 -2
  91. package/dist/{chunk-6ANPNUVL.js → chunk-IIXLB5E7.js} +58 -36
  92. package/dist/chunk-IIXLB5E7.js.map +1 -0
  93. package/dist/{chunk-SACPRJ7F.js → chunk-JNIJBXVK.js} +2 -2
  94. package/dist/{chunk-W3XT32W7.js → chunk-JVGUY2AU.js} +23 -13
  95. package/dist/chunk-JVGUY2AU.js.map +1 -0
  96. package/dist/{chunk-DGLON3AL.js → chunk-KNRG6ZGJ.js} +6 -6
  97. package/dist/{chunk-GEUDRENF.js → chunk-MMTG3SCJ.js} +2 -2
  98. package/dist/{chunk-WZFAPHIK.js → chunk-MU7ZHSZE.js} +2 -2
  99. package/dist/{chunk-W4FBGYGK.js → chunk-NRV4Y3PM.js} +1 -1
  100. package/dist/{chunk-QTO2FGBT.js → chunk-OUW4PAY4.js} +191 -117
  101. package/dist/chunk-OUW4PAY4.js.map +1 -0
  102. package/dist/{chunk-KQ2XCQYN.js → chunk-P2BLHUL2.js} +2 -2
  103. package/dist/{chunk-ACGGTPU4.js → chunk-QCKQEBIY.js} +17 -9
  104. package/dist/chunk-QCKQEBIY.js.map +1 -0
  105. package/dist/{chunk-6L775BUB.js → chunk-QE63OHLB.js} +2 -2
  106. package/dist/{chunk-ONHRRJZM.js → chunk-QJAQNXWI.js} +6 -6
  107. package/dist/{chunk-EQYBHXRR.js → chunk-ROUL7RZU.js} +385 -59
  108. package/dist/chunk-ROUL7RZU.js.map +1 -0
  109. package/dist/{chunk-N6M7MRVN.js → chunk-RT4TSR2C.js} +9034 -8726
  110. package/dist/chunk-RT4TSR2C.js.map +1 -0
  111. package/dist/{chunk-6NZ7U4R4.js → chunk-SCOT5DTA.js} +1 -1
  112. package/dist/chunk-SCOT5DTA.js.map +1 -0
  113. package/dist/{chunk-6RYWNBSA.js → chunk-SVZGA2MK.js} +2 -2
  114. package/dist/chunk-SYOIVK7K.js +63 -0
  115. package/dist/chunk-SYOIVK7K.js.map +1 -0
  116. package/dist/{chunk-DM5A4ZFL.js → chunk-TWCOYET2.js} +11 -1
  117. package/dist/chunk-TWCOYET2.js.map +1 -0
  118. package/dist/{chunk-7S5OMI76.js → chunk-WS32QRLO.js} +109 -44
  119. package/dist/chunk-WS32QRLO.js.map +1 -0
  120. package/dist/{chunk-OCMLGND7.js → chunk-WWZ35GWW.js} +5 -5
  121. package/dist/{chunk-QMGIQUBG.js → chunk-XWPFZX6S.js} +503 -139
  122. package/dist/chunk-XWPFZX6S.js.map +1 -0
  123. package/dist/{chunk-A5N6ISRK.js → chunk-ZN4UZWMT.js} +2 -2
  124. package/dist/{chunk-7M6APKJL.js → chunk-ZYU7ZFLT.js} +2 -2
  125. package/dist/{cli-D9vXgWwM.d.ts → cli-BrwT9YMD.d.ts} +5 -5
  126. package/dist/cli.d.ts +14 -11
  127. package/dist/cli.js +57 -56
  128. package/dist/compounding/engine.d.ts +2 -2
  129. package/dist/compounding/engine.js +5 -5
  130. package/dist/compounding/preference-consolidator.d.ts +1 -1
  131. package/dist/compression-optimizer.d.ts +1 -1
  132. package/dist/config.d.ts +1 -1
  133. package/dist/config.js +2 -2
  134. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  135. package/dist/connectors/codex-materialize-runner.js +5 -5
  136. package/dist/connectors/codex-materialize.d.ts +1 -1
  137. package/dist/connectors/index.d.ts +1 -1
  138. package/dist/connectors/index.js +5 -5
  139. package/dist/consolidation-provenance-check.d.ts +2 -2
  140. package/dist/consolidation-undo.d.ts +2 -2
  141. package/dist/contradiction/index.d.ts +2 -2
  142. package/dist/conversation-index/backend.d.ts +1 -1
  143. package/dist/conversation-index/chunker.d.ts +1 -1
  144. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  145. package/dist/conversation-index/indexer.d.ts +1 -1
  146. package/dist/conversation-index/search.d.ts +1 -1
  147. package/dist/day-summary.d.ts +1 -1
  148. package/dist/delinearize.d.ts +1 -1
  149. package/dist/direct-answer-wiring.d.ts +1 -1
  150. package/dist/direct-answer.d.ts +1 -1
  151. package/dist/embedding-fallback.d.ts +1 -1
  152. package/dist/enrichment/index.d.ts +1 -1
  153. package/dist/entity-retrieval.d.ts +2 -2
  154. package/dist/entity-retrieval.js +5 -5
  155. package/dist/entity-schema.d.ts +1 -1
  156. package/dist/explicit-capture.d.ts +9 -7
  157. package/dist/extraction-error-classification.d.ts +22 -0
  158. package/dist/extraction-error-classification.js +11 -0
  159. package/dist/extraction-error-classification.js.map +1 -0
  160. package/dist/extraction-faithfulness.d.ts +1 -1
  161. package/dist/extraction-judge-telemetry.d.ts +1 -1
  162. package/dist/extraction-judge-training.d.ts +1 -1
  163. package/dist/extraction-judge.d.ts +1 -1
  164. package/dist/extraction.d.ts +3 -17
  165. package/dist/extraction.js +5 -3
  166. package/dist/fallback-llm.d.ts +1 -1
  167. package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
  168. package/dist/graph-dashboard-diff.d.ts +1 -1
  169. package/dist/graph-dashboard-key.d.ts +1 -1
  170. package/dist/graph-dashboard-parser.d.ts +1 -1
  171. package/dist/graph-edge-reinforcement.d.ts +1 -1
  172. package/dist/graph-snapshot.d.ts +1 -1
  173. package/dist/graph.d.ts +1 -1
  174. package/dist/importance.d.ts +1 -1
  175. package/dist/importers/index.d.ts +1 -1
  176. package/dist/in-flight-reads.d.ts +1 -1
  177. package/dist/index.d.ts +713 -923
  178. package/dist/index.js +176 -140
  179. package/dist/intent.d.ts +1 -1
  180. package/dist/lcm/engine.d.ts +1 -1
  181. package/dist/lcm/engine.js +3 -3
  182. package/dist/lcm/index.d.ts +1 -1
  183. package/dist/lcm/index.js +11 -11
  184. package/dist/lcm/tools.d.ts +1 -1
  185. package/dist/lifecycle.d.ts +1 -1
  186. package/dist/live-connectors-runner.d.ts +1 -1
  187. package/dist/local-llm.d.ts +1 -1
  188. package/dist/local-model-endpoint.d.ts +1 -1
  189. package/dist/maintenance/memory-governance.d.ts +1 -1
  190. package/dist/maintenance/memory-governance.js +6 -6
  191. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  192. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  193. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  194. package/dist/maintenance/rebuild-memory-projection.js +7 -7
  195. package/dist/maintenance-C98njYb8.d.ts +366 -0
  196. package/dist/mcp-memory-inspector-app.d.ts +12 -9
  197. package/dist/mcp-read-only-tools.js +1 -1
  198. package/dist/memory-action-policy.d.ts +1 -1
  199. package/dist/memory-cache.d.ts +1 -1
  200. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  201. package/dist/memory-projection-store.d.ts +1 -1
  202. package/dist/memory-provenance.d.ts +1 -1
  203. package/dist/memory-worth-outcomes.d.ts +2 -2
  204. package/dist/models-json.d.ts +1 -1
  205. package/dist/namespaces/migrate.d.ts +3 -3
  206. package/dist/namespaces/migrate.js +12 -12
  207. package/dist/namespaces/principal.d.ts +1 -1
  208. package/dist/namespaces/search.d.ts +1 -1
  209. package/dist/namespaces/search.js +6 -6
  210. package/dist/namespaces/storage.d.ts +3 -3
  211. package/dist/namespaces/storage.js +5 -5
  212. package/dist/native-knowledge.d.ts +1 -1
  213. package/dist/offline-sync-impression-drain.d.ts +1 -1
  214. package/dist/operator-toolkit.d.ts +2 -2
  215. package/dist/operator-toolkit.js +18 -18
  216. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  217. package/dist/orchestration/maintenance.d.ts +6 -223
  218. package/dist/orchestration/maintenance.js +12 -12
  219. package/dist/{orchestrator-DB93S48I.d.ts → orchestrator-C9ZVppNh.d.ts} +12 -481
  220. package/dist/orchestrator.d.ts +10 -8
  221. package/dist/orchestrator.js +84 -82
  222. package/dist/patterns-cli.d.ts +1 -1
  223. package/dist/{pipeline-DmGc-H66.d.ts → pipeline-id_-sqYm.d.ts} +1 -1
  224. package/dist/policy-runtime.d.ts +1 -1
  225. package/dist/proactive-contention.d.ts +1 -1
  226. package/dist/provenance.d.ts +1 -1
  227. package/dist/{qmd-cJT0crkF.d.ts → qmd-R6XaQOZK.d.ts} +1 -1
  228. package/dist/qmd-preflight.d.ts +2 -2
  229. package/dist/qmd-recall-cache.d.ts +1 -1
  230. package/dist/qmd.d.ts +2 -2
  231. package/dist/qmd.js +4 -4
  232. package/dist/recall-concurrency-config.d.ts +1 -1
  233. package/dist/recall-disclosure-escalation.d.ts +1 -1
  234. package/dist/recall-explain-renderer.d.ts +1 -1
  235. package/dist/recall-explain-renderer.js +3 -3
  236. package/dist/recall-memory-map.d.ts +1 -1
  237. package/dist/recall-planner-llm.d.ts +1 -1
  238. package/dist/recall-state.d.ts +1 -1
  239. package/dist/recall-tag-filter.d.ts +1 -1
  240. package/dist/recall-timings.d.ts +1 -1
  241. package/dist/recall-xray-cli.d.ts +1 -1
  242. package/dist/recall-xray-cli.js +4 -4
  243. package/dist/recall-xray-renderer.d.ts +1 -1
  244. package/dist/recall-xray-renderer.js +3 -3
  245. package/dist/recall-xray.d.ts +1 -1
  246. package/dist/recall-xray.js +2 -2
  247. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  248. package/dist/replay/normalizers/claude.d.ts +1 -1
  249. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  250. package/dist/replay/normalizers/shared.d.ts +1 -1
  251. package/dist/replay/runner.d.ts +1 -1
  252. package/dist/replay/types.d.ts +1 -1
  253. package/dist/resolve-auth-token.d.ts +1 -1
  254. package/dist/resume-bundles.js +4 -4
  255. package/dist/retrieval-agents.d.ts +2 -2
  256. package/dist/retrieval-tiers.d.ts +1 -1
  257. package/dist/routing/engine.d.ts +1 -1
  258. package/dist/routing/store.d.ts +1 -1
  259. package/dist/salvage-envelope.d.ts +1 -1
  260. package/dist/schemas.d.ts +76 -76
  261. package/dist/{scope-profiles-KG2nODX7.d.ts → scope-profiles-EhyuAKkd.d.ts} +1 -1
  262. package/dist/search/embed-helper.d.ts +1 -1
  263. package/dist/search/factory.d.ts +1 -1
  264. package/dist/search/factory.js +5 -5
  265. package/dist/search/index.d.ts +1 -1
  266. package/dist/search/index.js +9 -9
  267. package/dist/search/lancedb-backend.d.ts +1 -1
  268. package/dist/search/meilisearch-backend.d.ts +1 -1
  269. package/dist/search/noop-backend.d.ts +1 -1
  270. package/dist/search/orama-backend.d.ts +1 -1
  271. package/dist/search/port.d.ts +1 -1
  272. package/dist/search/remote-backend.d.ts +1 -1
  273. package/dist/{semantic-consolidation-BaZlg0D8.d.ts → semantic-consolidation-B2xo26UQ.d.ts} +1 -1
  274. package/dist/semantic-consolidation.d.ts +2 -2
  275. package/dist/semantic-consolidation.js +7 -7
  276. package/dist/semantic-rule-promotion.js +5 -5
  277. package/dist/semantic-rule-verifier.d.ts +1 -1
  278. package/dist/semantic-rule-verifier.js +6 -6
  279. package/dist/service-DgFWlZ-r.d.ts +835 -0
  280. package/dist/session-observer-bands.d.ts +1 -1
  281. package/dist/session-observer-state.d.ts +1 -1
  282. package/dist/shared-context/manager.d.ts +9 -9
  283. package/dist/signal.d.ts +1 -1
  284. package/dist/{storage-BKhHHnRo.d.ts → storage-XGawwqXE.d.ts} +131 -2
  285. package/dist/storage.d.ts +2 -2
  286. package/dist/storage.js +4 -4
  287. package/dist/summarizer.d.ts +1 -1
  288. package/dist/summary-snapshot.d.ts +1 -1
  289. package/dist/temporal-supersession.d.ts +2 -2
  290. package/dist/temporal-timeline-recall.d.ts +1 -1
  291. package/dist/temporal-validity.d.ts +1 -1
  292. package/dist/threading.d.ts +1 -1
  293. package/dist/tier-migration.d.ts +2 -2
  294. package/dist/tier-routing.d.ts +1 -1
  295. package/dist/topics.d.ts +1 -1
  296. package/dist/transcript.d.ts +1 -1
  297. package/dist/transfer/types.d.ts +66 -66
  298. package/dist/trust-score-stage.d.ts +1 -1
  299. package/dist/trust-score.d.ts +1 -1
  300. package/dist/{types-GBQZuT7l.d.ts → types-Bbs0qz6b.d.ts} +2 -3
  301. package/dist/types.d.ts +1 -1
  302. package/dist/types.js +1 -1
  303. package/dist/utility-runtime.d.ts +1 -1
  304. package/dist/verified-recall.js +6 -6
  305. package/dist/write-envelope.d.ts +1 -1
  306. package/package.json +2 -2
  307. package/src/access-boundary.ts +3 -0
  308. package/src/access-http.test.ts +63 -0
  309. package/src/access-http.ts +26 -37
  310. package/src/access-mcp-output-schemas.ts +10 -0
  311. package/src/access-mcp.ts +4 -121
  312. package/src/access-operations-batch.ts +10 -5
  313. package/src/access-service.ts +43 -46
  314. package/src/access-surface-catalog.ts +6 -0
  315. package/src/access-wearables-meetings-surface.ts +139 -0
  316. package/src/activity/digest.ts +1 -1
  317. package/src/activity/runner.test.ts +37 -0
  318. package/src/activity/runner.ts +12 -0
  319. package/src/activity/scheduler.test.ts +1 -0
  320. package/src/activity/sync-registration.ts +13 -0
  321. package/src/cli/meetings-commands.ts +70 -0
  322. package/src/cli.ts +7 -6
  323. package/src/extraction-error-classification.ts +52 -0
  324. package/src/extraction.ts +2 -50
  325. package/src/mcp-read-only-tools.ts +2 -0
  326. package/src/meetings/build-memory.test.ts +364 -0
  327. package/src/meetings/build-scheduler.test.ts +55 -0
  328. package/src/meetings/build-scheduler.ts +96 -0
  329. package/src/meetings/day-source.test.ts +275 -0
  330. package/src/meetings/day-source.ts +352 -0
  331. package/src/meetings/http-glue.ts +126 -0
  332. package/src/meetings/index.ts +5 -4
  333. package/src/meetings/manual-sync-flush.test.ts +72 -0
  334. package/src/meetings/mcp-tools.ts +65 -0
  335. package/src/meetings/memory-gen.test.ts +498 -0
  336. package/src/meetings/memory-gen.ts +549 -0
  337. package/src/meetings/memory-generator.ts +5 -0
  338. package/src/meetings/namespace-isolation.test.ts +159 -0
  339. package/src/meetings/service.test.ts +195 -0
  340. package/src/meetings/service.ts +157 -0
  341. package/src/meetings/summary-extractor.test.ts +287 -0
  342. package/src/meetings/summary-extractor.ts +264 -0
  343. package/src/orchestration/maintenance.ts +8 -0
  344. package/src/orchestration/orchestrator-init.ts +6 -1
  345. package/src/orchestration/workspace-ops.ts +172 -8
  346. package/src/orchestrator.ts +12 -13
  347. package/src/storage.ts +21 -0
  348. package/src/testing/subjects/meetings-namespace-harness.ts +159 -0
  349. package/src/testing/subjects/meetings-namespace.test.ts +93 -0
  350. package/src/types.ts +1 -2
  351. package/src/wearables/cli.ts +2 -1
  352. package/src/wearables/index.ts +1 -0
  353. package/src/wearables/mcp-tools.ts +143 -0
  354. package/src/wearables/registry.test.ts +11 -0
  355. package/src/wearables/registry.ts +13 -0
  356. package/src/wearables/service.test.ts +62 -1
  357. package/src/wearables/service.ts +30 -2
  358. package/src/wearables/transcript-search-namespace-isolation.test.ts +98 -0
  359. package/dist/chunk-6ANPNUVL.js.map +0 -1
  360. package/dist/chunk-6NZ7U4R4.js.map +0 -1
  361. package/dist/chunk-7S5OMI76.js.map +0 -1
  362. package/dist/chunk-ACGGTPU4.js.map +0 -1
  363. package/dist/chunk-DM5A4ZFL.js.map +0 -1
  364. package/dist/chunk-EQYBHXRR.js.map +0 -1
  365. package/dist/chunk-IC2TCZJ2.js.map +0 -1
  366. package/dist/chunk-MONWCSVL.js.map +0 -1
  367. package/dist/chunk-N6M7MRVN.js.map +0 -1
  368. package/dist/chunk-QMGIQUBG.js.map +0 -1
  369. package/dist/chunk-QTO2FGBT.js.map +0 -1
  370. package/dist/chunk-SQEDDWAM.js.map +0 -1
  371. package/dist/chunk-W3XT32W7.js.map +0 -1
  372. /package/dist/{auto-sync-UPTUVE4X.js.map → auto-sync-5OE5QOZB.js.map} +0 -0
  373. /package/dist/{chunk-GF22HWS2.js.map → chunk-2PHX3I5C.js.map} +0 -0
  374. /package/dist/{chunk-JFDEYXF7.js.map → chunk-3NAHZQU3.js.map} +0 -0
  375. /package/dist/{chunk-XBRPEGXF.js.map → chunk-3UOSVTFR.js.map} +0 -0
  376. /package/dist/{chunk-TTKWTT72.js.map → chunk-3ZW6ZQO6.js.map} +0 -0
  377. /package/dist/{chunk-UV72DEZD.js.map → chunk-4B77XSG5.js.map} +0 -0
  378. /package/dist/{chunk-LMHDFOVV.js.map → chunk-5CPI2QWG.js.map} +0 -0
  379. /package/dist/{chunk-P72WZMWQ.js.map → chunk-5FZQ74K5.js.map} +0 -0
  380. /package/dist/{chunk-BRT7KEXU.js.map → chunk-6TWXQ4E2.js.map} +0 -0
  381. /package/dist/{chunk-CV6ZYXK3.js.map → chunk-7I7AYIUQ.js.map} +0 -0
  382. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  383. /package/dist/{chunk-JN57HZCD.js.map → chunk-ATEAL5EI.js.map} +0 -0
  384. /package/dist/{chunk-4PYLBXX6.js.map → chunk-AYAROCWD.js.map} +0 -0
  385. /package/dist/{chunk-NB2P47N7.js.map → chunk-CUUY5CC4.js.map} +0 -0
  386. /package/dist/{chunk-KL5Y6PEV.js.map → chunk-E3ZMFFQZ.js.map} +0 -0
  387. /package/dist/{chunk-55FDQYPS.js.map → chunk-ES5IHQ6M.js.map} +0 -0
  388. /package/dist/{chunk-N47ZWQBO.js.map → chunk-G2NSSQES.js.map} +0 -0
  389. /package/dist/{chunk-GSWPU5CR.js.map → chunk-GD3QOBMB.js.map} +0 -0
  390. /package/dist/{chunk-SACPRJ7F.js.map → chunk-JNIJBXVK.js.map} +0 -0
  391. /package/dist/{chunk-DGLON3AL.js.map → chunk-KNRG6ZGJ.js.map} +0 -0
  392. /package/dist/{chunk-GEUDRENF.js.map → chunk-MMTG3SCJ.js.map} +0 -0
  393. /package/dist/{chunk-WZFAPHIK.js.map → chunk-MU7ZHSZE.js.map} +0 -0
  394. /package/dist/{chunk-W4FBGYGK.js.map → chunk-NRV4Y3PM.js.map} +0 -0
  395. /package/dist/{chunk-KQ2XCQYN.js.map → chunk-P2BLHUL2.js.map} +0 -0
  396. /package/dist/{chunk-6L775BUB.js.map → chunk-QE63OHLB.js.map} +0 -0
  397. /package/dist/{chunk-ONHRRJZM.js.map → chunk-QJAQNXWI.js.map} +0 -0
  398. /package/dist/{chunk-6RYWNBSA.js.map → chunk-SVZGA2MK.js.map} +0 -0
  399. /package/dist/{chunk-OCMLGND7.js.map → chunk-WWZ35GWW.js.map} +0 -0
  400. /package/dist/{chunk-A5N6ISRK.js.map → chunk-ZN4UZWMT.js.map} +0 -0
  401. /package/dist/{chunk-7M6APKJL.js.map → chunk-ZYU7ZFLT.js.map} +0 -0
  402. /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
@@ -0,0 +1,549 @@
1
+ /**
2
+ * Meeting memory generation (issue #1900, Phase 4).
3
+ *
4
+ * Layer 1 — the deterministic meeting EPISODE: one recall-anchor memory per
5
+ * meeting ("what meetings did I have Tuesday?"), always written when
6
+ * `meetings.enabled`, no LLM. Title, time span, attendees, and source list are
7
+ * a pure function of the record, so an unchanged meeting is skipped by content
8
+ * hash. Reuses the shared sealed-envelope composer + secure write path
9
+ * (`composeMemoryEnvelope` / `writeSealedMemory`) — no bespoke memory format.
10
+ *
11
+ * The LLM summary/facts layer (decisions + commitments through the existing
12
+ * trust pipeline) builds on this module in the trust-gated slice; it reuses the
13
+ * same writer interface and the wearables judge/trust machinery rather than
14
+ * forking it.
15
+ */
16
+
17
+ import { scoreImportance } from "../importance.js";
18
+ import { composeMemoryEnvelope } from "../write-envelope.js";
19
+ import { log } from "../logger.js";
20
+ import { stripAttributesSuffix } from "../structured-attributes.js";
21
+ import type { MemoryWriteResult } from "../index.js";
22
+ import type { MemoryCategory, MemoryStatus } from "../types.js";
23
+ import {
24
+ type MeetingEpisodeGenResult,
25
+ type MeetingMemoryGenerator,
26
+ type MeetingMemoryOutcome,
27
+ type MeetingMemoryWriter,
28
+ type MeetingsDayFactGenResult,
29
+ } from "./memory-generator.js";
30
+ import { computeTrustScore, decideSmart, type TrustEvidence } from "../wearables/trust.js";
31
+ import { describeErrorForOperator } from "../wearables/errors.js";
32
+ import type { MeetingRecord, MeetingsConfig } from "./types.js";
33
+
34
+ /** Frontmatter `source` prefix for meeting-derived memories. */
35
+ export const MEETING_SOURCE_PREFIX = "meeting";
36
+ /** `source: meeting:<id>` — walks memory → record → day transcripts. */
37
+ export function meetingSourceLabel(id: string): string {
38
+ return `${MEETING_SOURCE_PREFIX}:${id}`;
39
+ }
40
+ /** `meeting-day:<date>` tag for day-scoped recall ("meetings on Tuesday"). */
41
+ export function meetingDayTag(date: string): string {
42
+ return `meeting-day:${date}`;
43
+ }
44
+ /** Render the deterministic episode content for a meeting. Pure. */
45
+ export function composeMeetingEpisodeContent(record: MeetingRecord): string {
46
+ const clock = (iso: string): string => {
47
+ const ms = Date.parse(iso);
48
+ return Number.isNaN(ms) ? iso : new Date(ms).toISOString().slice(11, 16);
49
+ };
50
+ const app = record.app !== undefined && record.app.length > 0 ? ` (${record.app})` : "";
51
+ const title = record.title !== undefined && record.title.length > 0 ? ` · ${record.title}` : "";
52
+ const attendees = record.attendees.length > 0 ? record.attendees.join(", ") : "no attendees resolved";
53
+ const sources = record.sources.length > 0 ? record.sources.join(", ") : "none";
54
+ return (
55
+ `Meeting ${record.id}${title} — ${record.date} ${clock(record.startUtc)}–${clock(record.endUtc)} UTC${app}.\n` +
56
+ `Attendees: ${attendees}.\n` +
57
+ `Transcript sources: ${sources}.`
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Write the deterministic episode memory for one meeting. Idempotent: skips
63
+ * when an identical episode already exists. Returns true when a new episode was
64
+ * written. The episode is `active` (a recall anchor, not a trust-gated claim).
65
+ */
66
+ export async function writeMeetingEpisodeMemory(
67
+ record: MeetingRecord,
68
+ writer: MeetingMemoryWriter,
69
+ ): Promise<boolean> {
70
+ const content = composeMeetingEpisodeContent(record);
71
+ if (await writer.hasMemoryFromSource(meetingSourceLabel(record.id), content)) return false;
72
+
73
+ const structuredAttributes: Record<string, string> = {
74
+ meetingId: record.id,
75
+ meetingDate: record.date,
76
+ transcriptSources: record.sources.join(","),
77
+ };
78
+ if (record.app !== undefined) structuredAttributes.meetingApp = record.app;
79
+
80
+ const envelope = composeMemoryEnvelope(
81
+ {
82
+ content,
83
+ category: "moment",
84
+ confidence: 0.9,
85
+ tags: [MEETING_SOURCE_PREFIX, meetingDayTag(record.date)],
86
+ validAt: record.startUtc,
87
+ structuredAttributes,
88
+ },
89
+ { source: meetingSourceLabel(record.id) },
90
+ { salvage: true },
91
+ );
92
+ if (envelope.salvageNotes.length > 0) {
93
+ log.warn(`meeting episode write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
94
+ }
95
+ await writer.writeSealedMemory(envelope, {
96
+ importance: scoreImportance(content, "moment", [MEETING_SOURCE_PREFIX]),
97
+ contentHashSource: content,
98
+ status: "active",
99
+ memoryKind: "episode",
100
+ });
101
+ return true;
102
+ }
103
+
104
+ /** Write one deterministic episode per record, idempotently. */
105
+ export async function generateMeetingEpisodes(
106
+ records: readonly MeetingRecord[],
107
+ writer: MeetingMemoryWriter,
108
+ ): Promise<MeetingEpisodeGenResult> {
109
+ let written = 0;
110
+ let skipped = 0;
111
+ for (const record of records) {
112
+ if (await writeMeetingEpisodeMemory(record, writer)) written++;
113
+ else skipped++;
114
+ }
115
+ return { written, skipped };
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Layer 2 — trust-gated LLM summary + facts (decisions, commitments, questions)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /** One LLM-extracted meeting claim. */
123
+ export interface MeetingFactCandidate {
124
+ /** Claim text (a decision, commitment, or open question). */
125
+ content: string;
126
+ /** Memory category — commitments carry the decay-aware `commitment` category. */
127
+ category: Extract<MemoryCategory, "fact" | "decision" | "commitment">;
128
+ /** Extractor confidence (0..1); defaults to 0.7 downstream when absent. */
129
+ confidence?: number;
130
+ }
131
+
132
+ /** LLM extractor over a meeting's fused transcript + screen context. Injected so
133
+ * `off` mode provably never invokes it and tests stay deterministic. */
134
+ export interface MeetingSummaryExtractor {
135
+ extract(input: {
136
+ record: MeetingRecord;
137
+ transcriptText: string;
138
+ screenContextText: string;
139
+ }): Promise<{ summary: string; candidates: MeetingFactCandidate[] }>;
140
+ }
141
+
142
+ /** Optional durability judge; verdicts align with the shared trust pipeline. */
143
+ export interface MeetingSummaryJudge {
144
+ judge(candidates: readonly MeetingFactCandidate[]): Promise<Array<"accept" | "reject" | "defer">>;
145
+ }
146
+
147
+ export interface MeetingSummaryDeps {
148
+ extractor: MeetingSummaryExtractor;
149
+ judge?: MeetingSummaryJudge;
150
+ writer: MeetingMemoryWriter;
151
+ }
152
+
153
+ export interface MeetingSummaryResult {
154
+ /** False when summaryMode is `off` (the extractor is never called). */
155
+ llmInvoked: boolean;
156
+ /** Facts written active. */
157
+ active: number;
158
+ /** Facts written to the review queue (pending_review). */
159
+ review: number;
160
+ /** Candidates dropped below the trust bar or judge-rejected. */
161
+ dropped: number;
162
+ /** Candidates/summary skipped because an identical memory already existed
163
+ * for this meeting (idempotent rebuild). */
164
+ skipped: number;
165
+ /** True when a non-empty summary memory was written. */
166
+ summaryWritten: boolean;
167
+ /** Facts the #1579 tombstone chokepoint downgraded to pending_review (no
168
+ * active copy). Counted here, NOT in `active`/`review`, so callers never
169
+ * report a blocked write as a successfully written fact (#1645). */
170
+ tombstoneBlocked: number;
171
+ /** Non-fatal degradation notes (e.g. the durability judge threw and the
172
+ * pass continued on trust bands alone). */
173
+ warnings: readonly string[];
174
+ }
175
+
176
+ /** Render the fused transcript as `**Speaker** [HH:MM]: text` for the prompt. */
177
+ function renderTranscriptText(record: MeetingRecord): string {
178
+ return record.transcript
179
+ .map((seg) => {
180
+ const ms = seg.startIso === undefined ? NaN : Date.parse(seg.startIso);
181
+ const clock = Number.isNaN(ms) ? "--:--" : new Date(ms).toISOString().slice(11, 16);
182
+ return `**${seg.speaker}** [${clock}]: ${seg.text}`;
183
+ })
184
+ .join("\n");
185
+ }
186
+
187
+ /** Render the screen-context timeline + excerpts for the prompt. */
188
+ function renderScreenContextText(record: MeetingRecord): string {
189
+ const lines = record.screenContext.map((e) => `[${e.clock}] ${e.label}`);
190
+ if (record.contextExcerpts.length > 0) lines.push(...record.contextExcerpts);
191
+ return lines.join("\n");
192
+ }
193
+
194
+ async function writeMeetingFact(
195
+ record: MeetingRecord,
196
+ candidate: MeetingFactCandidate,
197
+ status: Extract<MemoryStatus, "active" | "pending_review">,
198
+ writer: MeetingMemoryWriter,
199
+ ): Promise<MemoryWriteResult> {
200
+ const structuredAttributes: Record<string, string> = {
201
+ meetingId: record.id,
202
+ meetingDate: record.date,
203
+ transcriptSources: record.sources.join(","),
204
+ };
205
+ if (record.app !== undefined) structuredAttributes.meetingApp = record.app;
206
+ const envelope = composeMemoryEnvelope(
207
+ {
208
+ content: candidate.content,
209
+ category: candidate.category,
210
+ confidence: candidate.confidence,
211
+ tags: [MEETING_SOURCE_PREFIX, meetingDayTag(record.date), candidate.category],
212
+ validAt: record.startUtc,
213
+ structuredAttributes,
214
+ },
215
+ { source: meetingSourceLabel(record.id) },
216
+ { salvage: true },
217
+ );
218
+ if (envelope.salvageNotes.length > 0) {
219
+ log.warn(`meeting fact write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
220
+ }
221
+ return writer.writeSealedMemory(envelope, {
222
+ importance: scoreImportance(candidate.content, candidate.category, [MEETING_SOURCE_PREFIX]),
223
+ contentHashSource: candidate.content,
224
+ status,
225
+ memoryKind: "note",
226
+ });
227
+ }
228
+
229
+ /** Confidence assigned to the synthesized meeting-summary fact. */
230
+ const SUMMARY_FACT_CONFIDENCE = 0.8;
231
+
232
+ /**
233
+ * Route one meeting claim (candidate or the synthesized summary) to active /
234
+ * review / drop through the SAME trust decision. `review` mode never
235
+ * auto-actives (queue everything survivable); `smart` mode uses the shared
236
+ * band decision. A judge reject drops in both. Used for BOTH the extracted
237
+ * candidates and the summary text so a rejected/low-trust meeting can never
238
+ * push its summary active behind the gate's back.
239
+ */
240
+ function resolveMeetingFactOutcome(
241
+ config: MeetingsConfig,
242
+ trust: number,
243
+ judgeVerdict: "accept" | "reject" | "defer" | undefined,
244
+ ): "active" | "review" | "drop" {
245
+ if (config.summaryMode === "review") {
246
+ return judgeVerdict === "reject" ? "drop" : "review";
247
+ }
248
+ return decideSmart(trust, judgeVerdict, {
249
+ autoApproveTrust: config.autoApproveTrust,
250
+ reviewTrust: config.reviewTrust,
251
+ }).outcome;
252
+ }
253
+
254
+ /**
255
+ * Layer 2: extract a trust-gated summary + facts (decisions/commitments/open
256
+ * questions) for one meeting and persist the survivors. Reuses the shared trust
257
+ * pipeline (`computeTrustScore` + `decideSmart`), NOT a parallel scorer.
258
+ *
259
+ * - `off` → returns immediately; the extractor is NEVER invoked (episode-only).
260
+ * - `review`→ every candidate lands in the review queue (pending_review).
261
+ * - `smart` → judge verdict + trust bands route each candidate to active /
262
+ * review / drop; corroboration (>= 2 transcript sources) boosts trust.
263
+ */
264
+ export async function generateMeetingSummaryFacts(
265
+ record: MeetingRecord,
266
+ config: MeetingsConfig,
267
+ deps: MeetingSummaryDeps,
268
+ ): Promise<MeetingSummaryResult> {
269
+ const empty: MeetingSummaryResult = {
270
+ llmInvoked: false,
271
+ active: 0,
272
+ review: 0,
273
+ dropped: 0,
274
+ skipped: 0,
275
+ summaryWritten: false,
276
+ tombstoneBlocked: 0,
277
+ warnings: [],
278
+ };
279
+ if (config.summaryMode === "off") return empty;
280
+ if (record.transcript.length === 0) return empty; // nothing to summarize (no audio)
281
+
282
+ const { summary, candidates } = await deps.extractor.extract({
283
+ record,
284
+ transcriptText: renderTranscriptText(record),
285
+ screenContextText: renderScreenContextText(record),
286
+ });
287
+ const warnings: string[] = [];
288
+ const result: MeetingSummaryResult = { ...empty, llmInvoked: true, warnings };
289
+
290
+ // The durability judge is optional and external — a throw (provider outage,
291
+ // timeout, malformed response) must DEGRADE this pass, not abort the whole day
292
+ // build. Fall back to no verdicts so the trust bands still route each candidate
293
+ // (review mode → review; smart mode → trust-band decision) (#1900 round 2).
294
+ let verdicts: Array<"accept" | "reject" | "defer"> | undefined;
295
+ if (deps.judge) {
296
+ try {
297
+ verdicts = await deps.judge.judge(candidates);
298
+ } catch (err) {
299
+ warnings.push(
300
+ `meeting summary judge unavailable: ${describeErrorForOperator(err)} — trust scoring continued without judge verdicts`,
301
+ );
302
+ }
303
+ }
304
+ // Corroboration: a meeting covered by >= 2 transcript sources corroborates its
305
+ // claims (record.corroboratedBy already lists the out-voted sources).
306
+ const evidence: TrustEvidence = { corroboratedBySources: [...record.corroboratedBy] };
307
+
308
+ const source = meetingSourceLabel(record.id);
309
+ for (let i = 0; i < candidates.length; i++) {
310
+ const candidate = candidates[i]!;
311
+ const judgeVerdict = verdicts?.[i];
312
+ // Idempotent rebuild: an identical claim already persisted for THIS meeting
313
+ // must not be written a second time (facts are not in the fact-hash index).
314
+ if (await deps.writer.hasMemoryFromSource(source, candidate.content)) {
315
+ result.skipped++;
316
+ continue;
317
+ }
318
+ const trust = computeTrustScore({
319
+ extractionConfidence: candidate.confidence,
320
+ sourceTrust: config.sourceTrust,
321
+ judgeVerdict,
322
+ evidence,
323
+ });
324
+ const outcome = resolveMeetingFactOutcome(config, trust, judgeVerdict);
325
+ if (outcome === "drop") {
326
+ result.dropped++;
327
+ continue;
328
+ }
329
+ const status = outcome === "active" ? "active" : "pending_review";
330
+ const write = await writeMeetingFact(record, candidate, status, deps.writer);
331
+ if (write.tombstoneBlocked) {
332
+ // #1645: the tombstone chokepoint downgraded this fact to pending_review
333
+ // (no active copy). Count it as blocked, NEVER as the active/review write
334
+ // we requested, so the day summary never overstates persisted facts.
335
+ result.tombstoneBlocked++;
336
+ } else if (status === "active") {
337
+ result.active++;
338
+ } else {
339
+ result.review++;
340
+ }
341
+ }
342
+
343
+ // The synthesized summary is a meeting claim too: gate it through the SAME
344
+ // trust decision so a meeting whose candidates were all rejected / below the
345
+ // bar cannot slip its summary in as an active fact (it was force-active before).
346
+ const summaryText = summary.trim();
347
+ if (summaryText.length > 0) {
348
+ if (await deps.writer.hasMemoryFromSource(source, summaryText)) {
349
+ result.skipped++;
350
+ } else {
351
+ const summaryTrust = computeTrustScore({
352
+ extractionConfidence: SUMMARY_FACT_CONFIDENCE,
353
+ sourceTrust: config.sourceTrust,
354
+ evidence,
355
+ });
356
+ const outcome = resolveMeetingFactOutcome(config, summaryTrust, undefined);
357
+ if (outcome === "drop") {
358
+ result.dropped++;
359
+ } else {
360
+ const write = await writeMeetingFact(
361
+ record,
362
+ { content: summaryText, category: "fact", confidence: SUMMARY_FACT_CONFIDENCE },
363
+ outcome === "active" ? "active" : "pending_review",
364
+ deps.writer,
365
+ );
366
+ // #1645: a tombstone-blocked summary landed pending_review (no active
367
+ // copy) — record it as blocked, not as a written summary, so the day
368
+ // aggregate never reports a summary that was actually gated out.
369
+ if (write.tombstoneBlocked) result.tombstoneBlocked++;
370
+ else result.summaryWritten = true;
371
+ }
372
+ }
373
+ }
374
+ return result;
375
+ }
376
+
377
+ /**
378
+ * Storage capabilities `createMeetingMemoryWriter` needs, satisfied structurally
379
+ * by `StorageManager`. Kept narrow (the same rationale as the wearables writer)
380
+ * so this module performs NO direct fs and never touches the secure-store key.
381
+ */
382
+ export interface MeetingMemoryStorageIo {
383
+ writeSealedMemory: MeetingMemoryWriter["writeSealedMemory"];
384
+ readAllMemories(): Promise<
385
+ Array<{
386
+ path: string;
387
+ frontmatter: { id: string; source: string; status?: string };
388
+ content: string;
389
+ }>
390
+ >;
391
+ invalidateMemory(id: string): Promise<boolean>;
392
+ }
393
+
394
+ /**
395
+ * Build the meeting memory writer used by production builds. Mirrors
396
+ * `createWearableMemoryWriter`: the storage fact-hash index only covers
397
+ * category "fact", so meeting episodes (`moment`) and decisions/commitments
398
+ * dedup by a bounded, source-scoped content scan over `meeting:<id>`-tagged
399
+ * memories — without it, a forced/retried day re-writes identical episodes and
400
+ * facts (issue #1900).
401
+ */
402
+ export function createMeetingMemoryWriter(storage: MeetingMemoryStorageIo): MeetingMemoryWriter {
403
+ return {
404
+ writeSealedMemory: storage.writeSealedMemory.bind(storage),
405
+ hasMemoryFromSource: async (source: string, content: string): Promise<boolean> => {
406
+ // Stored bodies carry the "[Attributes: ...]" enrichment suffix; callers
407
+ // pass raw text — strip on BOTH sides so attribute-bearing memories match.
408
+ const needle = stripAttributesSuffix(content);
409
+ const memories = await storage.readAllMemories();
410
+ return memories.some(
411
+ (memory) =>
412
+ memory.frontmatter.source === source &&
413
+ memory.frontmatter.status !== "rejected" &&
414
+ stripAttributesSuffix(memory.content) === needle,
415
+ );
416
+ },
417
+ retireMemoriesFromSource: async (source: string): Promise<number> => {
418
+ const memories = await storage.readAllMemories();
419
+ let retired = 0;
420
+ for (const memory of memories) {
421
+ if (memory.frontmatter.source !== source) continue;
422
+ if (await storage.invalidateMemory(memory.frontmatter.id)) retired++;
423
+ }
424
+ return retired;
425
+ },
426
+ };
427
+ }
428
+
429
+ /**
430
+ * Fold the per-record trust-gated summary/fact results into one day aggregate.
431
+ * The deterministic builder used to own this loop; it now lives behind the
432
+ * memory seam so the engine never calls `generateMeetingSummaryFacts`.
433
+ */
434
+ async function generateMeetingDayFacts(
435
+ records: readonly MeetingRecord[],
436
+ config: MeetingsConfig,
437
+ writer: MeetingMemoryWriter,
438
+ summaryDeps: { extractor: MeetingSummaryExtractor; judge?: MeetingSummaryJudge },
439
+ warnings: string[],
440
+ ): Promise<MeetingsDayFactGenResult> {
441
+ const agg: MeetingsDayFactGenResult = {
442
+ llmInvoked: false,
443
+ active: 0,
444
+ review: 0,
445
+ dropped: 0,
446
+ skipped: 0,
447
+ summariesWritten: 0,
448
+ };
449
+ for (const record of records) {
450
+ const result = await generateMeetingSummaryFacts(record, config, {
451
+ extractor: summaryDeps.extractor,
452
+ ...(summaryDeps.judge !== undefined ? { judge: summaryDeps.judge } : {}),
453
+ writer,
454
+ });
455
+ if (result.llmInvoked) agg.llmInvoked = true;
456
+ agg.active += result.active;
457
+ agg.review += result.review;
458
+ agg.dropped += result.dropped;
459
+ agg.skipped += result.skipped;
460
+ if (result.summaryWritten) agg.summariesWritten++;
461
+ if (result.tombstoneBlocked > 0) {
462
+ agg.tombstoneBlocked = (agg.tombstoneBlocked ?? 0) + result.tombstoneBlocked;
463
+ }
464
+ if (result.warnings.length > 0) warnings.push(...result.warnings);
465
+ }
466
+ return agg;
467
+ }
468
+
469
+ /**
470
+ * Concrete `MeetingMemoryGenerator` for the builder's memory seam (issue #1900
471
+ * decouple). Implements `onRecordsBuilt` by driving the EXISTING episode/fact/
472
+ * retract logic behind the interface the deterministic engine depends on, and
473
+ * routes each built id by the partition the engine computed from the store's
474
+ * per-record write result (round 2):
475
+ *
476
+ * - `removedIds` — retire the deleted meetings' memories so recall never
477
+ * surfaces a meeting `show <id>` can no longer load;
478
+ * - `updatedIds` — the record changed under a kept id: retract the prior
479
+ * episode/summary for `meeting:<id>` FIRST, then regenerate from the new
480
+ * record, so recall is refreshed (not duplicated, not left stale);
481
+ * - `unchangedIds` — the stored record was byte-identical this rebuild: SKIP
482
+ * all generation for it (no episode probe, no extractor call, no rewrite) so
483
+ * an idempotent rebuild fires zero LLM calls and writes nothing;
484
+ * - new ids (built minus unchanged minus updated) — generate fresh.
485
+ *
486
+ * `reindexNeeded` fires when the regenerated records wrote new episodes/facts;
487
+ * the builder additionally reindexes on removed/built counts, so a retract-only
488
+ * rebuild still reindexes.
489
+ *
490
+ * `config` is captured here (not passed through `onRecordsBuilt`) because the
491
+ * seam input carries record partitions, while the summary/facts layer needs
492
+ * `summaryMode` and the trust bands.
493
+ */
494
+ export function createMeetingMemoryGenerator(
495
+ writer: MeetingMemoryWriter,
496
+ config: MeetingsConfig,
497
+ summaryDeps?: { extractor: MeetingSummaryExtractor; judge?: MeetingSummaryJudge },
498
+ ): MeetingMemoryGenerator {
499
+ return {
500
+ async onRecordsBuilt({ built, removedIds, unchangedIds, updatedIds }): Promise<MeetingMemoryOutcome> {
501
+ // Retract deleted meetings' memories (stale removals).
502
+ for (const removedId of removedIds) {
503
+ await writer.retireMemoriesFromSource(meetingSourceLabel(removedId));
504
+ }
505
+ // Retract the prior memories of records that changed under a kept id, so the
506
+ // regeneration below REFRESHES the meeting (never leaves stale episodes/facts
507
+ // beside the new ones). The updated records are still in `built` and regenerate.
508
+ for (const updatedId of updatedIds) {
509
+ await writer.retireMemoriesFromSource(meetingSourceLabel(updatedId));
510
+ }
511
+ // Generate changed + new records always. An unchanged record regenerates ONLY
512
+ // when its deterministic episode memory is ABSENT: a prior build persisted the
513
+ // record but its generation threw before the memories landed, so the store now
514
+ // reports the record in unchangedIds (contentHash identical) and a plain skip
515
+ // would strand the meeting with zero memories forever. "Unchanged" means skip
516
+ // only when the memories already exist. The episode is the sentinel — it is
517
+ // always written first (deterministic, no LLM), so an absent episode means the
518
+ // source is unwritten. Unchanged records whose episode already exists still skip,
519
+ // so an idempotent rebuild fires zero LLM calls and writes nothing.
520
+ const unchanged = new Set(unchangedIds);
521
+ const toGenerate: MeetingRecord[] = [];
522
+ for (const record of built) {
523
+ if (!unchanged.has(record.id)) {
524
+ toGenerate.push(record);
525
+ continue;
526
+ }
527
+ const episodePresent = await writer.hasMemoryFromSource(
528
+ meetingSourceLabel(record.id),
529
+ composeMeetingEpisodeContent(record),
530
+ );
531
+ if (!episodePresent) toGenerate.push(record);
532
+ }
533
+ const warnings: string[] = [];
534
+ const outcome: MeetingMemoryOutcome = { reindexNeeded: false, warnings };
535
+ if (toGenerate.length > 0) {
536
+ outcome.episodes = await generateMeetingEpisodes(toGenerate, writer);
537
+ if (summaryDeps !== undefined && config.summaryMode !== "off") {
538
+ outcome.facts = await generateMeetingDayFacts(toGenerate, config, writer, summaryDeps, warnings);
539
+ }
540
+ }
541
+ const episodesWrote = (outcome.episodes?.written ?? 0) > 0;
542
+ const factsWrote =
543
+ outcome.facts !== undefined &&
544
+ outcome.facts.active + outcome.facts.review + outcome.facts.summariesWritten > 0;
545
+ outcome.reindexNeeded = episodesWrote || factsWrote;
546
+ return outcome;
547
+ },
548
+ };
549
+ }
@@ -69,6 +69,11 @@ export interface MeetingsDayFactGenResult {
69
69
  skipped: number;
70
70
  /** Count of records whose summary memory was written. */
71
71
  summariesWritten: number;
72
+ /** Facts the #1579 tombstone chokepoint downgraded to pending_review (no
73
+ * active copy). Surfaced distinctly from `active`/`review` so callers never
74
+ * count a blocked write as a successfully written active/review fact (#1645).
75
+ * Optional so the seam's canned outcomes stay valid when unset. */
76
+ tombstoneBlocked?: number;
72
77
  }
73
78
 
74
79
  /**