@remnic/core 9.52.2 → 9.54.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 (429) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +22 -22
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +23 -23
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +22 -22
  7. package/dist/access-cli.js +55 -55
  8. package/dist/access-coding-context-resolution.d.ts +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +11 -11
  10. package/dist/access-extraction-force-flush.js +22 -22
  11. package/dist/access-health-types.d.ts +1 -1
  12. package/dist/access-http-lcm-compaction.d.ts +11 -11
  13. package/dist/access-http-lifecycle-flush.d.ts +11 -11
  14. package/dist/access-http-offline-stream.d.ts +11 -11
  15. package/dist/access-http-query.js +22 -22
  16. package/dist/access-http.d.ts +11 -11
  17. package/dist/access-http.js +29 -29
  18. package/dist/access-identity-continuity-surface.d.ts +9 -9
  19. package/dist/access-identity-continuity-surface.js +22 -22
  20. package/dist/access-lcm-surface.d.ts +11 -11
  21. package/dist/access-lcm-surface.js +22 -22
  22. package/dist/access-mcp.d.ts +11 -11
  23. package/dist/access-mcp.js +27 -27
  24. package/dist/access-memory-search-fanout.d.ts +2 -2
  25. package/dist/{access-namespace-preflight-Ds6CGlCP.d.ts → access-namespace-preflight-C7n4tgWC.d.ts} +1 -1
  26. package/dist/access-namespace-preflight.d.ts +3 -3
  27. package/dist/access-namespace-preflight.js +22 -22
  28. package/dist/access-observe-write-surface.d.ts +11 -11
  29. package/dist/access-observe-write-surface.js +22 -22
  30. package/dist/access-offline-manifest.d.ts +9 -9
  31. package/dist/access-offline-manifest.js +8 -4
  32. package/dist/access-operations-batch.js +24 -24
  33. package/dist/access-operations.d.ts +14 -14
  34. package/dist/access-operations.js +26 -26
  35. package/dist/access-recall-concurrency.d.ts +11 -11
  36. package/dist/access-recall-concurrency.js +22 -22
  37. package/dist/access-recall-response.d.ts +11 -11
  38. package/dist/access-recall-response.js +22 -22
  39. package/dist/access-recall-surface.d.ts +11 -11
  40. package/dist/access-recall-surface.js +22 -22
  41. package/dist/access-schema.d.ts +88 -88
  42. package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CXvbxLK-.d.ts} +7 -7
  43. package/dist/access-service-helpers.d.ts +11 -11
  44. package/dist/access-service.d.ts +11 -11
  45. package/dist/access-service.js +22 -22
  46. package/dist/access-surface-catalog.d.ts +11 -11
  47. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  48. package/dist/action-confidence.d.ts +1 -1
  49. package/dist/active-memory-bridge.d.ts +1 -1
  50. package/dist/active-recall.d.ts +1 -1
  51. package/dist/active-recall.js +5 -5
  52. package/dist/ambient-provenance.d.ts +1 -1
  53. package/dist/artifact-search.d.ts +1 -1
  54. package/dist/{auto-sync-QFQGOEO2.js → auto-sync-TDHLZPVP.js} +7 -7
  55. package/dist/behavior-learner.d.ts +1 -1
  56. package/dist/behavior-signals.d.ts +1 -1
  57. package/dist/bootstrap.d.ts +9 -9
  58. package/dist/briefing.d.ts +2 -2
  59. package/dist/briefing.js +12 -12
  60. package/dist/buffer-surprise-report.d.ts +1 -1
  61. package/dist/buffer-turn-helpers.d.ts +1 -1
  62. package/dist/buffer.d.ts +2 -2
  63. package/dist/bulk-import/index.d.ts +3 -3
  64. package/dist/calibration.d.ts +1 -1
  65. package/dist/capabilities.d.ts +1 -1
  66. package/dist/{catalog-D3B4RDy1.d.ts → catalog-BOOxl-I2.d.ts} +1 -1
  67. package/dist/causal-behavior.d.ts +1 -1
  68. package/dist/causal-consolidation.d.ts +1 -1
  69. package/dist/causal-consolidation.js +13 -13
  70. package/dist/causal-trajectory-graph.d.ts +1 -1
  71. package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
  72. package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
  73. package/dist/{chunk-O5FFBO2M.js → chunk-47P3RYZP.js} +70 -8
  74. package/dist/{chunk-O5FFBO2M.js.map → chunk-47P3RYZP.js.map} +1 -1
  75. package/dist/{chunk-X7XEDOSL.js → chunk-4IIFL7GC.js} +8 -8
  76. package/dist/chunk-5FVVY2EB.js +27 -0
  77. package/dist/chunk-5FVVY2EB.js.map +1 -0
  78. package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
  79. package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
  80. package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
  81. package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
  82. package/dist/chunk-6R4G7N4S.js.map +1 -0
  83. package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
  84. package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
  85. package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
  86. package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
  87. package/dist/chunk-CX5SW2EW.js.map +1 -0
  88. package/dist/{chunk-RMHMUUBW.js → chunk-CYF6WVE7.js} +105 -49
  89. package/dist/{chunk-RMHMUUBW.js.map → chunk-CYF6WVE7.js.map} +1 -1
  90. package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
  91. package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
  92. package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
  93. package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
  94. package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
  95. package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
  96. package/dist/{chunk-IARXXZU4.js → chunk-HOIRB77A.js} +99 -20
  97. package/dist/chunk-HOIRB77A.js.map +1 -0
  98. package/dist/{chunk-NDAH7BJ5.js → chunk-HVP33QVW.js} +103 -6
  99. package/dist/chunk-HVP33QVW.js.map +1 -0
  100. package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
  101. package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
  102. package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
  103. package/dist/chunk-JDSVLVCG.js.map +1 -0
  104. package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
  105. package/dist/{chunk-D5YOZ66N.js → chunk-JV5YUBIZ.js} +20 -20
  106. package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
  107. package/dist/chunk-K4QS7LXU.js.map +1 -0
  108. package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
  109. package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
  110. package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
  111. package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
  112. package/dist/chunk-MLPTFODV.js.map +1 -0
  113. package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
  114. package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
  115. package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
  116. package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
  117. package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
  118. package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
  119. package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
  120. package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
  121. package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
  122. package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
  123. package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
  124. package/dist/chunk-THPPCISA.js.map +1 -0
  125. package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
  126. package/dist/{chunk-XAPMEU3K.js → chunk-UU7TRFNE.js} +2 -2
  127. package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
  128. package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
  129. package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
  130. package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
  131. package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
  132. package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
  133. package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
  134. package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
  135. package/dist/{cli-DMAFI0qt.d.ts → cli-DVUFIa5E.d.ts} +5 -5
  136. package/dist/cli.d.ts +13 -13
  137. package/dist/cli.js +42 -42
  138. package/dist/coding/pre-action-gate.d.ts +1 -1
  139. package/dist/compounding/engine.d.ts +2 -2
  140. package/dist/compounding/engine.js +13 -13
  141. package/dist/compounding/preference-consolidator.d.ts +1 -1
  142. package/dist/compression-optimizer.d.ts +1 -1
  143. package/dist/config.d.ts +1 -1
  144. package/dist/config.js +5 -5
  145. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  146. package/dist/connectors/codex-materialize-runner.js +12 -12
  147. package/dist/connectors/codex-materialize.d.ts +1 -1
  148. package/dist/connectors/index.d.ts +1 -1
  149. package/dist/connectors/index.js +12 -12
  150. package/dist/consolidation-provenance-check.d.ts +2 -2
  151. package/dist/consolidation-undo.d.ts +2 -2
  152. package/dist/content-hash.d.ts +12 -2
  153. package/dist/content-hash.js +9 -3
  154. package/dist/contradiction/index.d.ts +2 -2
  155. package/dist/contradiction/index.js +3 -3
  156. package/dist/converge-config.d.ts +1 -1
  157. package/dist/convergence-refresh.d.ts +2 -2
  158. package/dist/convergence-refresh.js +14 -14
  159. package/dist/conversation-index/backend.d.ts +1 -1
  160. package/dist/conversation-index/chunker.d.ts +1 -1
  161. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  162. package/dist/conversation-index/indexer.d.ts +1 -1
  163. package/dist/conversation-index/search.d.ts +1 -1
  164. package/dist/corpus-watermark.d.ts +1 -1
  165. package/dist/corpus-watermark.js +14 -14
  166. package/dist/day-summary.d.ts +1 -1
  167. package/dist/delinearize.d.ts +1 -1
  168. package/dist/dependency-propagation-config.d.ts +1 -1
  169. package/dist/{dependency-propagation-delivery-D63r24pH.d.ts → dependency-propagation-delivery-XzF76C9n.d.ts} +1 -1
  170. package/dist/direct-answer-wiring.d.ts +1 -1
  171. package/dist/direct-answer.d.ts +1 -1
  172. package/dist/embedding-fallback.d.ts +1 -1
  173. package/dist/enrichment/index.d.ts +1 -1
  174. package/dist/entity-retrieval.d.ts +2 -2
  175. package/dist/entity-retrieval.js +12 -12
  176. package/dist/entity-schema.d.ts +1 -1
  177. package/dist/explicit-capture.d.ts +9 -9
  178. package/dist/explicit-capture.js +8 -8
  179. package/dist/external-wiki-access.d.ts +11 -11
  180. package/dist/external-wiki-access.js +23 -23
  181. package/dist/external-wiki-collection-registration.d.ts +1 -1
  182. package/dist/external-wiki-collection.d.ts +1 -1
  183. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  184. package/dist/extraction-error-classification.d.ts +1 -1
  185. package/dist/extraction-faithfulness.d.ts +1 -1
  186. package/dist/extraction-judge-telemetry.d.ts +1 -1
  187. package/dist/extraction-judge-training.d.ts +1 -1
  188. package/dist/extraction-judge.d.ts +1 -1
  189. package/dist/extraction-liveness.d.ts +1 -1
  190. package/dist/extraction-normalization.d.ts +1 -1
  191. package/dist/extraction-normalization.js +2 -2
  192. package/dist/extraction-prompt.d.ts +1 -1
  193. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  194. package/dist/extraction-source-grounding.d.ts +1 -1
  195. package/dist/extraction-source-grounding.js +3 -3
  196. package/dist/extraction.d.ts +1 -1
  197. package/dist/extraction.js +16 -16
  198. package/dist/fallback-llm.d.ts +1 -1
  199. package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
  200. package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
  201. package/dist/graph-dashboard-diff.d.ts +1 -1
  202. package/dist/graph-dashboard-key.d.ts +1 -1
  203. package/dist/graph-dashboard-parser.d.ts +1 -1
  204. package/dist/graph-edge-reinforcement.d.ts +1 -1
  205. package/dist/graph-path-reconstruction.d.ts +1 -1
  206. package/dist/graph-path-scoring.d.ts +1 -1
  207. package/dist/graph-snapshot.d.ts +1 -1
  208. package/dist/graph.d.ts +1 -1
  209. package/dist/harmonic-construction.js +2 -2
  210. package/dist/harmonic-retrieval.js +4 -4
  211. package/dist/importance.d.ts +1 -1
  212. package/dist/importers/index.d.ts +1 -1
  213. package/dist/in-flight-reads.d.ts +1 -1
  214. package/dist/index.d.ts +777 -705
  215. package/dist/index.js +77 -67
  216. package/dist/intent.d.ts +1 -1
  217. package/dist/lcm/engine.d.ts +1 -1
  218. package/dist/lcm/index.d.ts +1 -1
  219. package/dist/lcm/tools.d.ts +1 -1
  220. package/dist/lifecycle.d.ts +1 -1
  221. package/dist/live-connectors-runner.d.ts +1 -1
  222. package/dist/local-llm.d.ts +1 -1
  223. package/dist/local-model-endpoint.d.ts +1 -1
  224. package/dist/maintenance/memory-governance.d.ts +1 -1
  225. package/dist/maintenance/memory-governance.js +12 -12
  226. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  227. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
  228. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  229. package/dist/maintenance/rebuild-memory-projection.js +13 -13
  230. package/dist/{maintenance-kBII2qUI.d.ts → maintenance-BJzNIKBE.d.ts} +3 -3
  231. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  232. package/dist/memory-action-policy.d.ts +1 -1
  233. package/dist/memory-cache.d.ts +1 -1
  234. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  235. package/dist/memory-projection-store.d.ts +1 -1
  236. package/dist/memory-provenance.d.ts +1 -1
  237. package/dist/memory-snapshot.d.ts +1 -1
  238. package/dist/memory-worth-outcomes.d.ts +2 -2
  239. package/dist/models-json.d.ts +1 -1
  240. package/dist/namespaces/migrate.d.ts +3 -3
  241. package/dist/namespaces/migrate.js +13 -13
  242. package/dist/namespaces/principal.d.ts +1 -1
  243. package/dist/namespaces/search.d.ts +1 -1
  244. package/dist/namespaces/storage.d.ts +3 -3
  245. package/dist/namespaces/storage.js +12 -12
  246. package/dist/native-knowledge.d.ts +1 -1
  247. package/dist/offline-sync-impression-drain.d.ts +1 -1
  248. package/dist/operator-doctor-corpus.d.ts +1 -1
  249. package/dist/operator-doctor-corpus.js +15 -15
  250. package/dist/operator-doctor-replica.d.ts +1 -1
  251. package/dist/operator-toolkit.d.ts +3 -3
  252. package/dist/operator-toolkit.js +21 -21
  253. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  254. package/dist/orchestration/maintenance.d.ts +4 -4
  255. package/dist/orchestration/maintenance.js +17 -17
  256. package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-BiXgBJBQ.d.ts} +8 -8
  257. package/dist/orchestrator.d.ts +9 -9
  258. package/dist/orchestrator.js +55 -55
  259. package/dist/patterns-cli.d.ts +1 -1
  260. package/dist/{pipeline-D21Rs6gW.d.ts → pipeline-sz_dcuLg.d.ts} +1 -1
  261. package/dist/policy-runtime.d.ts +1 -1
  262. package/dist/proactive-contention.d.ts +1 -1
  263. package/dist/provenance.d.ts +1 -1
  264. package/dist/provenance.js +2 -2
  265. package/dist/{qmd-Dhle1P4X.d.ts → qmd-eAWObK4M.d.ts} +1 -1
  266. package/dist/qmd-preflight.d.ts +2 -2
  267. package/dist/qmd-recall-cache.d.ts +1 -1
  268. package/dist/qmd.d.ts +2 -2
  269. package/dist/recall-concurrency-config.d.ts +1 -1
  270. package/dist/recall-disclosure-escalation.d.ts +1 -1
  271. package/dist/recall-explain-renderer.d.ts +1 -1
  272. package/dist/recall-memory-map.d.ts +1 -1
  273. package/dist/recall-planner-llm.d.ts +1 -1
  274. package/dist/recall-state.d.ts +1 -1
  275. package/dist/recall-tag-filter.d.ts +1 -1
  276. package/dist/recall-timings.d.ts +1 -1
  277. package/dist/recall-xray-cli.d.ts +1 -1
  278. package/dist/recall-xray-renderer.d.ts +1 -1
  279. package/dist/recall-xray.d.ts +1 -1
  280. package/dist/reconcile/cursor.d.ts +1 -1
  281. package/dist/reconcile/manifest.d.ts +4 -2
  282. package/dist/reconcile/manifest.js +7 -3
  283. package/dist/reconcile/plan.d.ts +1 -1
  284. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  285. package/dist/replay/normalizers/claude.d.ts +1 -1
  286. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  287. package/dist/replay/normalizers/shared.d.ts +1 -1
  288. package/dist/replay/runner.d.ts +1 -1
  289. package/dist/replay/types.d.ts +1 -1
  290. package/dist/replica-divergence.d.ts +1 -1
  291. package/dist/replica-peers-config.d.ts +1 -1
  292. package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
  293. package/dist/resolve-auth-token.d.ts +1 -1
  294. package/dist/resume-bundles.js +6 -6
  295. package/dist/retrieval-agents.d.ts +2 -2
  296. package/dist/retrieval-tiers.d.ts +1 -1
  297. package/dist/routing/engine.d.ts +1 -1
  298. package/dist/routing/store.d.ts +1 -1
  299. package/dist/salvage-envelope.d.ts +1 -1
  300. package/dist/salvage-envelope.js +3 -3
  301. package/dist/schemas.d.ts +138 -138
  302. package/dist/{scope-profiles-NzGPDXLn.d.ts → scope-profiles-BBQsXuyj.d.ts} +1 -1
  303. package/dist/search/embed-helper.d.ts +1 -1
  304. package/dist/search/factory.d.ts +1 -1
  305. package/dist/search/index.d.ts +1 -1
  306. package/dist/search/lancedb-backend.d.ts +1 -1
  307. package/dist/search/meilisearch-backend.d.ts +1 -1
  308. package/dist/search/noop-backend.d.ts +1 -1
  309. package/dist/search/orama-backend.d.ts +1 -1
  310. package/dist/search/port.d.ts +1 -1
  311. package/dist/search/remote-backend.d.ts +1 -1
  312. package/dist/{semantic-consolidation-DLDKvyy5.d.ts → semantic-consolidation-HD8RkzVA.d.ts} +1 -1
  313. package/dist/semantic-consolidation.d.ts +2 -2
  314. package/dist/semantic-consolidation.js +13 -13
  315. package/dist/semantic-rule-promotion.js +12 -12
  316. package/dist/semantic-rule-verifier.d.ts +1 -1
  317. package/dist/semantic-rule-verifier.js +12 -12
  318. package/dist/{service-PT2_z7xm.d.ts → service-DIrtHHEg.d.ts} +2 -2
  319. package/dist/session-observer-bands.d.ts +1 -1
  320. package/dist/session-observer-state.d.ts +1 -1
  321. package/dist/shared-context/manager.d.ts +9 -9
  322. package/dist/signal.d.ts +1 -1
  323. package/dist/source-agent-qualifier.d.ts +1 -1
  324. package/dist/source-agent-qualifier.js +14 -14
  325. package/dist/{storage-BmVudhk4.d.ts → storage-DqCzXi56.d.ts} +37 -33
  326. package/dist/storage.d.ts +2 -2
  327. package/dist/storage.js +14 -14
  328. package/dist/structured-attributes.d.ts +3 -1
  329. package/dist/structured-attributes.js +3 -1
  330. package/dist/summarizer.d.ts +1 -1
  331. package/dist/summary-snapshot.d.ts +1 -1
  332. package/dist/temporal-supersession.d.ts +2 -2
  333. package/dist/temporal-timeline-recall.d.ts +1 -1
  334. package/dist/temporal-validity.d.ts +1 -1
  335. package/dist/threading.d.ts +1 -1
  336. package/dist/tier-migration.d.ts +2 -2
  337. package/dist/tier-routing.d.ts +1 -1
  338. package/dist/topics.d.ts +1 -1
  339. package/dist/transcript.d.ts +1 -1
  340. package/dist/transfer/types.d.ts +66 -66
  341. package/dist/trust-score-stage.d.ts +1 -1
  342. package/dist/trust-score.d.ts +1 -1
  343. package/dist/{types-CkiDUorT.d.ts → types-d_j4vC6N.d.ts} +26 -1
  344. package/dist/types.d.ts +1 -1
  345. package/dist/utility-runtime.d.ts +1 -1
  346. package/dist/verified-recall.js +12 -12
  347. package/dist/write-envelope.d.ts +1 -1
  348. package/dist/write-envelope.js +2 -2
  349. package/package.json +2 -2
  350. package/src/access-offline-manifest.ts +1 -0
  351. package/src/access-service-offline-file-content.test.ts +1 -0
  352. package/src/content-hash.ts +34 -1
  353. package/src/dedup/index.test.ts +56 -38
  354. package/src/dedup/index.ts +85 -38
  355. package/src/lifecycle/tombstones.test.ts +763 -16
  356. package/src/lifecycle/tombstones.ts +217 -50
  357. package/src/reconcile/manifest.test.ts +158 -1
  358. package/src/reconcile/manifest.ts +76 -22
  359. package/src/storage/content-hash-index.ts +56 -40
  360. package/src/storage/entity-ref-repair.ts +3 -3
  361. package/src/storage/memory-read-store.ts +9 -0
  362. package/src/storage/tombstone-blocked-capture-index.ts +4 -9
  363. package/src/storage-contract/content-hash.test.ts +13 -0
  364. package/src/storage-fact-hash-index-defer.test.ts +1 -1
  365. package/src/storage.ts +107 -93
  366. package/src/structured-attributes.ts +11 -0
  367. package/src/wearables/cleanup.test.ts +71 -0
  368. package/src/wearables/cleanup.ts +163 -16
  369. package/src/wearables/config.test.ts +62 -0
  370. package/src/wearables/config.ts +99 -5
  371. package/src/wearables/index.ts +10 -0
  372. package/src/wearables/pipeline.test.ts +36 -0
  373. package/src/wearables/pipeline.ts +18 -3
  374. package/src/wearables/redaction.test.ts +156 -0
  375. package/src/wearables/redaction.ts +102 -10
  376. package/src/wearables/text-language.ts +146 -0
  377. package/src/wearables/types.ts +26 -0
  378. package/dist/chunk-BQIFZ2MP.js.map +0 -1
  379. package/dist/chunk-DLG5V6ND.js.map +0 -1
  380. package/dist/chunk-IARXXZU4.js.map +0 -1
  381. package/dist/chunk-JJRVPWFY.js.map +0 -1
  382. package/dist/chunk-NDAH7BJ5.js.map +0 -1
  383. package/dist/chunk-R3DJVOSM.js.map +0 -1
  384. package/dist/chunk-VHFXP27N.js.map +0 -1
  385. package/dist/chunk-W5H6ZRPT.js +0 -14
  386. package/dist/chunk-W5H6ZRPT.js.map +0 -1
  387. package/dist/chunk-WD5B5AIE.js.map +0 -1
  388. /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-TDHLZPVP.js.map} +0 -0
  389. /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
  390. /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
  391. /package/dist/{chunk-X7XEDOSL.js.map → chunk-4IIFL7GC.js.map} +0 -0
  392. /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
  393. /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
  394. /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
  395. /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
  396. /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
  397. /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
  398. /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
  399. /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
  400. /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
  401. /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
  402. /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
  403. /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
  404. /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
  405. /package/dist/{chunk-D5YOZ66N.js.map → chunk-JV5YUBIZ.js.map} +0 -0
  406. /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
  407. /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
  408. /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
  409. /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
  410. /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
  411. /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
  412. /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
  413. /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
  414. /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
  415. /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
  416. /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
  417. /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
  418. /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
  419. /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
  420. /package/dist/{chunk-XAPMEU3K.js.map → chunk-UU7TRFNE.js.map} +0 -0
  421. /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
  422. /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
  423. /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
  424. /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
  425. /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
  426. /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
  427. /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
  428. /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
  429. /package/dist/{resolution-MDPNOEG6.js.map → resolution-MMOD5WC4.js.map} +0 -0
package/src/storage.ts CHANGED
@@ -13,7 +13,11 @@ import {
13
13
  } from "node:fs/promises";
14
14
  import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "node:fs";
15
15
  import { createHash } from "node:crypto";
16
- import { normalizeContent, computeContentHash } from "./content-hash.js";
16
+ import {
17
+ computeContentHash,
18
+ computeLegacyContentHash,
19
+ normalizeContent,
20
+ } from "./content-hash.js";
17
21
  import { raceAbort } from "./abort-error.js";
18
22
  import { checkCorpusReadAbort, type CorpusReadOptions } from "./corpus-read-cancellation.js";
19
23
  import { selectArtifactMatches, type ArtifactSearchOptions } from "./artifact-search.js";
@@ -115,7 +119,6 @@ import {
115
119
  sortStructuredSectionsBySchema,
116
120
  } from "./entity-schema.js";
117
121
  import {
118
- hasCitation,
119
122
  hasCitationForTemplate,
120
123
  stripCitationForTemplate,
121
124
  DEFAULT_CITATION_FORMAT,
@@ -1058,7 +1061,7 @@ const FACT_HASH_INDEX_REBUILD_RETRY_BASE_MS = 50;
1058
1061
  // the coding surfaces + wearable service). Imported here so internal callers
1059
1062
  // (snapshotBeforeWrite, snapshotForProvenance) resolve, and re-exported to
1060
1063
  // keep the public storage API stable for existing callers (wearables, dist).
1061
- import { assemblePersistedBody, stripAttributesSuffix } from "./structured-attributes.js";
1064
+ import { assemblePersistedBody, storedContentIdentityCandidates, stripAttributesSuffix } from "./structured-attributes.js";
1062
1065
  export { stripAttributesSuffix };
1063
1066
 
1064
1067
  // `normalizeAttributePairs` moved to ./structured-attributes.ts (issue #1989
@@ -3009,22 +3012,51 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3009
3012
  }
3010
3013
 
3011
3014
  private buildTombstoneStore(): TombstoneStore {
3015
+ let sourcePathsPromise: Promise<string[]> | undefined;
3016
+ let sourceContentsByIdPromise: Promise<Map<string, readonly string[]>> | undefined;
3012
3017
  const options: TombstoneStoreOptions = {
3013
3018
  enabled: this.tombstonesConfig.enabled,
3014
3019
  semanticMatch: this.tombstonesConfig.semanticMatch,
3015
3020
  semanticThreshold: this.tombstonesConfig.semanticThreshold,
3016
- // Wire the SAME helpers the dedup index uses (rule 23 / checklist §13):
3017
- // hashContent = ContentHashIndex.computeHash, normalizeText =
3018
- // ContentHashIndex.normalizeContent. Importing them here (not copying)
3019
- // guarantees the tombstone tiers can never drift from dedup.
3020
3021
  hashContent: ContentHashIndex.computeHash,
3021
3022
  normalizeText: ContentHashIndex.normalizeContent,
3023
+ sourceContentsForMemoryIds: async (sourceMemoryIds) => {
3024
+ const requested = new Set(sourceMemoryIds);
3025
+ const sourcePaths = await (sourcePathsPromise ??=
3026
+ this.memoryReadStore.collectTombstoneMigrationPaths());
3027
+ const directPaths = sourcePaths.filter((filePath) =>
3028
+ requested.has(path.basename(filePath, ".md"))
3029
+ );
3030
+ const contents = new Map<string, readonly string[]>();
3031
+ for (const memory of await this.readParsedMemoriesFromPaths(directPaths, 50)) {
3032
+ const id = memory.frontmatter.id;
3033
+ if (requested.has(id) && !contents.has(id)) {
3034
+ contents.set(id, this.storedContentIdentityCandidates(memory.content));
3035
+ }
3036
+ }
3037
+ if (contents.size === requested.size) return contents;
3038
+ sourceContentsByIdPromise ??= (async () => {
3039
+ const allContents = new Map<string, readonly string[]>();
3040
+ for (const memory of await this.readParsedMemoriesFromPaths(sourcePaths, 50)) {
3041
+ const id = memory.frontmatter.id;
3042
+ if (!allContents.has(id)) {
3043
+ allContents.set(id, this.storedContentIdentityCandidates(memory.content));
3044
+ }
3045
+ }
3046
+ return allContents;
3047
+ })();
3048
+ const allContents = await sourceContentsByIdPromise;
3049
+ for (const id of requested) {
3050
+ const content = allContents.get(id);
3051
+ if (content !== undefined) contents.set(id, content);
3052
+ }
3053
+ return contents;
3054
+ },
3022
3055
  };
3023
3056
  const io: TombstoneFileIo = {
3024
3057
  read: (filePath) => this.readStorageSecureFile(filePath),
3025
3058
  append: (filePath, content) => this.appendStorageSecureFile(filePath, content),
3026
3059
  write: (filePath, content) => this.writeStorageSecureFile(filePath, content),
3027
- // stat lets the store own its cross-process staleness probe (#1579).
3028
3060
  stat: (filePath) => statSync(filePath),
3029
3061
  };
3030
3062
  return new TombstoneStore(this.tombstonesPath, this.tombstonesConfig.namespace, options, io);
@@ -3263,13 +3295,14 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3263
3295
  }
3264
3296
  this.factHashIndexAuthoritative = false;
3265
3297
  this.factHashIndexAuthoritativePromise = (async () => {
3266
- // Round 11: ALWAYS rebuild the fact-hash index from the durable corpus on
3267
- // first use per process no on-disk "ready" marker is written or trusted,
3268
- // so a deferred write, crash, or multi-process interleave can never leave a
3269
- // stale index trusted. The scan AND publish run under the SAME per-index
3270
- // cross-process lock the reconciling saves use (rebuildUnderLock), so the
3271
- // rebuild can never overwrite a peer's newer lock-merged or deferred
3272
- // additions with an unlocked overwrite.
3298
+ // ALWAYS rebuild the index from the durable corpus on first use per
3299
+ // process, preserving the #2016 crash/restart invariant. Markerless
3300
+ // legacy indexes additionally require body-derived hashes because their
3301
+ // frontmatter.contentHash values may use the old normalizer.
3302
+ //
3303
+ // The scan and publish run under the SAME per-index cross-process lock
3304
+ // used by reconcile saves, so migration cannot overwrite a peer's newer
3305
+ // additions with an unlocked write.
3273
3306
  //
3274
3307
  // PR #2016 (findings 1-2): bounded-retry the LOCKED rebuild so transient
3275
3308
  // contention (a peer mid reconcile-save) clears within a short budget
@@ -3284,7 +3317,9 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3284
3317
  const baseMs = this.factHashIndexLockOptions.retryBaseMs ?? FACT_HASH_INDEX_REBUILD_RETRY_BASE_MS;
3285
3318
  let published = false;
3286
3319
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
3287
- published = await factHashIndex.rebuildUnderLock(() => this.rebuildFactHashIndexFromCorpus(factHashIndex));
3320
+ published = await factHashIndex.rebuildUnderLock(() =>
3321
+ this.rebuildFactHashIndexFromCorpus(factHashIndex)
3322
+ );
3288
3323
  if (published || attempt === maxAttempts - 1) break;
3289
3324
  const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);
3290
3325
  await new Promise<void>((resolve) => setTimeout(resolve, wait));
@@ -3346,17 +3381,16 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3346
3381
  // citation-strip reconstruction below, hashing the stored persist body
3347
3382
  // (title + steps) exactly as buildProcedurePersistBody registered it.
3348
3383
  if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
3349
- const hash = this.corpusRegisteredHash(memory);
3350
- if (hash === null) {
3351
- // Body carries a citation from an unknown/custom template we cannot
3352
- // safely strip — skip rather than register a wrong hash. A
3353
- // false-negative miss beats a wrong entry that would permanently
3354
- // suppress legitimate duplicate writes (see corpusRegisteredHash).
3384
+ const hashes = this.corpusRegisteredHashes(memory);
3385
+ if (hashes.length === 0) {
3386
+ // Skip bodies whose configured citation cannot be stripped safely.
3355
3387
  legacyRecovered++;
3356
3388
  continue;
3357
3389
  }
3358
- factHashIndex.addByHash(hash);
3359
- if (memory.frontmatter.category === "fact") this.factOnlyHashes.add(hash);
3390
+ for (const hash of hashes) {
3391
+ factHashIndex.addByHash(hash);
3392
+ if (memory.frontmatter.category === "fact") this.factOnlyHashes.add(hash);
3393
+ }
3360
3394
  }
3361
3395
  if (legacyRecovered > 0) {
3362
3396
  log.info(
@@ -3364,39 +3398,28 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3364
3398
  );
3365
3399
  }
3366
3400
  }
3367
- /**
3368
- * The content-hash the corpus rebuild registers for `memory`, or null when the
3369
- * body carries a citation from an unknown/custom template that cannot be
3370
- * safely stripped. Shared by the authoritative rebuild and the fact-only
3371
- * corpus confirmation (`hasFactContentHash`) so both derive the identical
3372
- * hash for a given stored body.
3373
- *
3374
- * Preference order:
3375
- * 1. frontmatter.contentHash — the raw pre-citation hash writeMemory records
3376
- * for facts (issue #369 round 8); matches hasFactContentHash(rawFact).
3377
- * 2. Reconstruct from the stored body: strip the "[Attributes: …]" suffix
3378
- * writeMemory appends for structuredAttributes (registration hashed the
3379
- * raw canonical content WITHOUT it — a no-op when absent), then strip a
3380
- * recognised citation and hash the bare body, or hash a citation-free
3381
- * body as-is.
3382
- * 3. A body with a citation from an unknown/custom template → null: a
3383
- * false-negative miss beats a wrong hash that would permanently suppress
3384
- * legitimate duplicate writes.
3385
- */
3386
- private corpusRegisteredHash(memory: MemoryFile): string | null {
3387
- if (memory.frontmatter.contentHash) {
3388
- return memory.frontmatter.contentHash;
3389
- }
3390
- const content = stripAttributesSuffix(memory.content);
3391
- const stripped = stripCitationForTemplate(content, this.citationTemplate);
3392
- if (stripped !== content) {
3393
- return ContentHashIndex.computeHash(sanitizeMemoryContent(stripped).text);
3394
- }
3395
- if (!hasCitation(content)) {
3396
- return ContentHashIndex.computeHash(sanitizeMemoryContent(content).text);
3397
- }
3398
- return null;
3401
+ private storedContentIdentityCandidates(content: string): string[] {
3402
+ return storedContentIdentityCandidates(content, (value) =>
3403
+ hasCitationForTemplate(value, this.citationTemplate) ? stripCitationForTemplate(value, this.citationTemplate) : value
3404
+ );
3405
+ }
3406
+
3407
+ /** Returns the current body identity while preserving explicit external identities. */
3408
+ private corpusRegisteredHashes(memory: MemoryFile): string[] {
3409
+ const persistedHash = memory.frontmatter.contentHash;
3410
+ const candidates = this.storedContentIdentityCandidates(memory.content)
3411
+ .map((content) => sanitizeMemoryContent(content).text);
3412
+ const currentHashes = candidates.map((content) => ContentHashIndex.computeHash(content));
3413
+ if (persistedHash && currentHashes.includes(persistedHash)) return [persistedHash];
3414
+ if (!persistedHash) return [currentHashes[0] ?? ContentHashIndex.computeHash("")];
3415
+ const legacyMatches = candidates.filter(
3416
+ (content) => computeLegacyContentHash(content) === persistedHash
3417
+ );
3418
+ return legacyMatches.length > 0
3419
+ ? [...new Set(legacyMatches.map((content) => ContentHashIndex.computeHash(content)))]
3420
+ : [persistedHash];
3399
3421
  }
3422
+
3400
3423
  private get questionsDir(): string {
3401
3424
  return path.join(this.baseDir, "questions");
3402
3425
  }
@@ -3787,10 +3810,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3787
3810
  let tombstoneBlocked = false;
3788
3811
  let gateRef: string | undefined;
3789
3812
  let statusBeforeBlock: MemoryFrontmatter["status"];
3790
- // Closure so the post-persist repair can RE-RUN it when a journal move
3791
- // changed the final entityRef: a verdict is only valid for the ref it was
3792
- // computed under, so reset and re-evaluate (parked mappings fall BACK to
3793
- // the legacy claimant; entity-independent tiers re-block in the lookup).
3813
+ // Re-run after journal moves that change the final entityRef.
3794
3814
  const applyTombstoneGate = async (): Promise<void> => {
3795
3815
  if (tombstoneBlocked) {
3796
3816
  if (fm.entityRef === gateRef) return;
@@ -3799,11 +3819,9 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3799
3819
  delete fm.blockedBy;
3800
3820
  delete fm.tombstoneBlockTier;
3801
3821
  }
3802
- // Status semantics (thread ObteQ: pending_review candidates included,
3803
- // terminal statuses respected) live in applyTombstoneResurrectionGate.
3804
3822
  if (category !== "fact" || factHashSourceForTombstone === null) return;
3805
3823
  const statusBefore = fm.status;
3806
- const match = await this.entityRefRepair.gate(fm, factHashSourceForTombstone, options.structuredAttributes);
3824
+ const match = await this.entityRefRepair.gate(fm, sanitized.text, options.structuredAttributes);
3807
3825
  if (match) {
3808
3826
  gateRef = fm.entityRef;
3809
3827
  statusBeforeBlock = statusBefore;
@@ -3980,34 +3998,28 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
3980
3998
  for (const memory of existing) {
3981
3999
  if (memory.frontmatter.category !== "fact") continue;
3982
4000
  if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
3983
- if (this.corpusRegisteredHash(memory) === targetHash) return true;
4001
+ if (this.corpusRegisteredHashes(memory).includes(targetHash)) return true;
3984
4002
  }
3985
4003
  return false;
3986
4004
  }
3987
4005
 
3988
- private factContentHashForRemoval(memory: MemoryFile): string | null {
3989
- if (memory.frontmatter.category !== "fact") return null;
3990
- if (typeof memory.frontmatter.contentHash === "string" && memory.frontmatter.contentHash.length > 0) {
3991
- return memory.frontmatter.contentHash;
3992
- }
3993
- const configuredHashSource = stripCitationMarkersForHashRemoval(memory.content, this.citationTemplate);
3994
- const hashSource =
3995
- configuredHashSource !== memory.content
3996
- ? configuredHashSource
3997
- : stripDefaultCitationMarkersWithoutRegex(memory.content);
3998
- return ContentHashIndex.computeHash(sanitizeMemoryContent(hashSource).text);
4006
+ private factContentHashesForRemoval(memory: MemoryFile): string[] {
4007
+ if (memory.frontmatter.category !== "fact") return [];
4008
+ return this.corpusRegisteredHashes(memory);
3999
4009
  }
4000
4010
 
4001
4011
  private async addActiveFactContentHash(memory: MemoryFile): Promise<void> {
4002
4012
  if (memory.frontmatter.category !== "fact") return;
4003
4013
  if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") return;
4004
- const hash = this.factContentHashForRemoval(memory);
4005
- if (!hash) return;
4014
+ const hashes = this.factContentHashesForRemoval(memory);
4015
+ if (hashes.length === 0) return;
4006
4016
 
4007
4017
  await this.ensureFactHashIndexAuthoritative();
4008
4018
  const factHashIndex = await this.getFactHashIndex();
4009
- factHashIndex.addByHash(hash);
4010
- this.factOnlyHashes.add(hash);
4019
+ for (const hash of hashes) {
4020
+ factHashIndex.addByHash(hash);
4021
+ this.factOnlyHashes.add(hash);
4022
+ }
4011
4023
  // PR #2016 thread SDzOP: flush through the SAME cross-process locked
4012
4024
  // reconcile the write/batch/rebuild paths use, never the unlocked whole-file
4013
4025
  // save() — an unlocked overwrite drops a concurrent extraction's appended
@@ -4045,16 +4057,19 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
4045
4057
  private async syncFactHashIndexAfterRewrite(before: MemoryFile, after: MemoryFile): Promise<void> {
4046
4058
  if (before.frontmatter.category !== "fact" && after.frontmatter.category !== "fact") return;
4047
4059
 
4048
- const beforeHash = this.factContentHashForRemoval(before);
4049
- const afterHash = this.factContentHashForRemoval(after);
4060
+ const beforeHashes = this.factContentHashesForRemoval(before);
4061
+ const afterHashes = this.factContentHashesForRemoval(after);
4050
4062
  const beforeStatus = inferMemoryStatus(before.frontmatter, before.path);
4051
4063
  const afterStatus = inferMemoryStatus(after.frontmatter, after.path);
4052
- if (beforeHash === afterHash && beforeStatus === afterStatus) return;
4064
+ const hashesChanged =
4065
+ beforeHashes.length !== afterHashes.length ||
4066
+ beforeHashes.some((hash, index) => hash !== afterHashes[index]);
4067
+ if (!hashesChanged && beforeStatus === afterStatus) return;
4053
4068
 
4054
- if (beforeStatus === "active" && beforeHash && (beforeHash !== afterHash || afterStatus !== "active")) {
4069
+ if (beforeStatus === "active" && beforeHashes.length > 0 && (hashesChanged || afterStatus !== "active")) {
4055
4070
  await this.removeFactContentHashesForMemories([before]);
4056
4071
  }
4057
- if (afterStatus === "active" && afterHash && (beforeHash !== afterHash || beforeStatus !== "active")) {
4072
+ if (afterStatus === "active" && afterHashes.length > 0 && (hashesChanged || beforeStatus !== "active")) {
4058
4073
  await this.addActiveFactContentHash(after);
4059
4074
  }
4060
4075
  }
@@ -4067,11 +4082,10 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
4067
4082
  .map((memory) => memory.frontmatter.id)
4068
4083
  .filter((id): id is string => typeof id === "string" && id.length > 0)
4069
4084
  );
4070
- const removedHashes = new Map<MemoryFile, string>();
4085
+ const removedHashes = new Set<string>();
4071
4086
  for (const memory of memories) {
4072
- const hash = this.factContentHashForRemoval(memory);
4073
- if (hash) {
4074
- removedHashes.set(memory, hash);
4087
+ for (const hash of this.factContentHashesForRemoval(memory)) {
4088
+ removedHashes.add(hash);
4075
4089
  }
4076
4090
  }
4077
4091
  if (removedHashes.size === 0) return;
@@ -4082,13 +4096,12 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
4082
4096
  if (memory.frontmatter.category !== "fact") continue;
4083
4097
  if (removedIds.has(memory.frontmatter.id)) continue;
4084
4098
  if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
4085
- const hash = this.factContentHashForRemoval(memory);
4086
- if (hash) {
4099
+ for (const hash of this.factContentHashesForRemoval(memory)) {
4087
4100
  remainingActiveHashes.add(hash);
4088
4101
  }
4089
4102
  }
4090
4103
 
4091
- for (const hash of removedHashes.values()) {
4104
+ for (const hash of removedHashes) {
4092
4105
  if (!remainingActiveHashes.has(hash)) {
4093
4106
  factHashIndex.removeByHash(hash);
4094
4107
  this.factOnlyHashes.delete(hash);
@@ -4117,8 +4130,9 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
4117
4130
  */
4118
4131
  removeFactOnlyHashForMemory(memory: MemoryFile): void {
4119
4132
  if (memory.frontmatter.category !== "fact") return;
4120
- const hash = this.factContentHashForRemoval(memory);
4121
- if (hash) this.factOnlyHashes.delete(hash);
4133
+ for (const hash of this.factContentHashesForRemoval(memory)) {
4134
+ this.factOnlyHashes.delete(hash);
4135
+ }
4122
4136
  }
4123
4137
 
4124
4138
  async isFactContentHashAuthoritative(): Promise<boolean> {
@@ -39,6 +39,17 @@ export function stripAttributesSuffix(content: string): string {
39
39
  if (inner.includes("]") || inner.includes("\n")) return content.trim();
40
40
  return trimmed.slice(0, markerIndex).trim();
41
41
  }
42
+
43
+ /** Return stored-body identity forms, from raw semantic content to full body. */
44
+ export function storedContentIdentityCandidates(
45
+ content: string,
46
+ stripCitation: (value: string) => string,
47
+ ): string[] {
48
+ const fullBody = content.trim();
49
+ const bodyWithoutAttributes = stripAttributesSuffix(fullBody);
50
+ const rawBody = stripCitation(bodyWithoutAttributes);
51
+ return [...new Set([rawBody, bodyWithoutAttributes, fullBody])];
52
+ }
42
53
  // ---------------------------------------------------------------------------
43
54
  // Persisted-body assembly (issue #1989 PR2)
44
55
  // ---------------------------------------------------------------------------
@@ -188,3 +188,74 @@ test("preserveUtteranceBoundaries default (off) still collapses generic labels",
188
188
  assert.equal(result.conversation.segments.length, 1);
189
189
  assert.equal(result.conversation.segments[0].text, "First. Second.");
190
190
  });
191
+
192
+ test("strips Japanese filler tokens that carry no word spaces", () => {
193
+ assert.equal(
194
+ stripFillerTokens("えーと、明日の会議は三時です"),
195
+ "明日の会議は三時です",
196
+ );
197
+ assert.equal(stripFillerTokens("あのー来週で"), "来週で");
198
+ });
199
+
200
+ test("keeps a meaning-bearing Japanese demonstrative", () => {
201
+ assert.equal(stripFillerTokens("あの会議は長かった"), "あの会議は長かった");
202
+ });
203
+
204
+ test("strips Korean and Cyrillic filler tokens as whole tokens", () => {
205
+ assert.equal(stripFillerTokens("음 회의는 세시입니다"), "회의는 세시입니다");
206
+ assert.equal(stripFillerTokens("эм встреча в три"), "встреча в три");
207
+ });
208
+
209
+ test("never strips a filler lookalike inside a longer token", () => {
210
+ assert.equal(stripFillerTokens("the umbrella is uhh wet"), "the umbrella is wet");
211
+ // "음" also occurs inside ordinary Korean words; only the whole token goes.
212
+ assert.equal(stripFillerTokens("다음 회의"), "다음 회의");
213
+ });
214
+
215
+ test("operator filler tokens apply to any script", () => {
216
+ assert.equal(stripFillerTokens("bueno vamos a empezar", ["bueno"]), "vamos a empezar");
217
+ assert.equal(stripFillerTokens("那个我们开始吧", ["那个"]), "我们开始吧");
218
+ });
219
+
220
+ test("cleanConversation strips configured filler tokens", () => {
221
+ const result = cleanConversation(
222
+ conversation([
223
+ { speakerKey: "a", text: "えーと、予算を確認します" },
224
+ { speakerKey: "b", text: "bueno, de acuerdo" },
225
+ ]),
226
+ ALL_ON,
227
+ ["bueno"],
228
+ );
229
+ assert.deepEqual(
230
+ result.conversation.segments.map((segment) => segment.text),
231
+ ["予算を確認します", "de acuerdo"],
232
+ );
233
+ });
234
+
235
+ test("an Arabic filler followed by Arabic punctuation is stripped", () => {
236
+ assert.equal(stripFillerTokens("اه، نعم"), "نعم");
237
+ });
238
+
239
+ test("a longer operator token wins over a built-in prefix", () => {
240
+ assert.equal(stripFillerTokens("あのーさあ始めよう", ["あのーさあ"]), "始めよう");
241
+ });
242
+
243
+ test("matcher cache keys distinguish differently-shaped token lists", () => {
244
+ assert.equal(stripFillerTokens("uh,um is here", ["uh,um"]), "is here");
245
+ assert.equal(stripFillerTokens("uh,um is here", ["uh", "um"]), "uh,um is here");
246
+ });
247
+
248
+ test("a Hebrew operator token is a whole token, never a substring", () => {
249
+ // Hebrew spaces its words, so an inline match would strip the suffix of
250
+ // an ordinary word and corrupt the transcript.
251
+ assert.equal(stripFillerTokens("נראה טוב", ["אה"]), "נראה טוב");
252
+ assert.equal(stripFillerTokens("אה טוב", ["אה"]), "טוב");
253
+ });
254
+
255
+ test("a decomposed accented filler is stripped without rewriting the rest", () => {
256
+ const decomposed = "a\u0308hm weiter mit dem cafe\u0301";
257
+ const cleaned = stripFillerTokens(decomposed);
258
+ assert.equal(cleaned, "weiter mit dem cafe\u0301");
259
+ // The surviving text keeps the ASR's own bytes: no NFC rewrite.
260
+ assert.ok(cleaned.includes("\u0301"));
261
+ });
@@ -7,6 +7,7 @@
7
7
  * everything here is conservative and reversible by re-syncing.
8
8
  */
9
9
 
10
+ import { detectScriptHints, type ScriptHint } from "./text-language.js";
10
11
  import type {
11
12
  WearableCleanupSettings,
12
13
  WearableConversation,
@@ -33,24 +34,155 @@ const MERGE_GAP_MS = 30_000;
33
34
  const GENERIC_SPEAKER_PATTERN = /^unknown$/i;
34
35
 
35
36
  /**
36
- * Standalone filler tokens stripped when `stripFillers` is on. Matched
37
- * case-insensitively on word boundaries, only as whole tokens — "um"
38
- * inside "umbrella" is never touched. Deliberately short, low-risk
39
- * list; meaning-bearing hedges ("like", "well") are NOT stripped.
37
+ * Standalone filler tokens stripped when `stripFillers` is on, for
38
+ * scripts that separate words with spaces. Matched case-insensitively
39
+ * as whole tokens — "um" inside "umbrella" is never touched.
40
+ * Deliberately short and low-risk: meaning-bearing hedges ("like",
41
+ * "well", Spanish "este", Arabic "يعني") are NOT stripped, and neither
42
+ * is "mm", which is also a unit of length.
40
43
  */
41
- const FILLER_TOKENS = ["um", "uh", "uhm", "umm", "uhh", "erm", "hmm", "mhm"];
44
+ const TOKEN_FILLERS: Readonly<Partial<Record<ScriptHint, readonly string[]>>> = {
45
+ latin: [
46
+ "um",
47
+ "uh",
48
+ "uhm",
49
+ "umm",
50
+ "uhh",
51
+ "erm",
52
+ "hmm",
53
+ "mhm",
54
+ "ähm",
55
+ "äh",
56
+ "ehm",
57
+ "euh",
58
+ "mmm",
59
+ ],
60
+ korean: ["음", "으음", "엄", "어어"],
61
+ arabic: ["امم", "اممم", "اه"],
62
+ cyrillic: ["эм", "ээ", "ммм"],
63
+ };
42
64
 
43
- const FILLER_PATTERN = new RegExp(
44
- // Leading/trailing punctuation around the filler collapses with it so
45
- // "Um, so we should" -> "so we should" rather than ", so we should".
46
- `(?:^|\\s)(?:${FILLER_TOKENS.join("|")})[,.]?(?=\\s|$)`,
47
- "gi",
48
- );
65
+ /**
66
+ * Filler tokens for scripts without word spaces. These are removed
67
+ * inline, so each entry must be unambiguous on its own: the elongated
68
+ * Japanese hesitation forms and the two Chinese hesitation particles
69
+ * qualify, while a bare 「あの」 ("that") does not (issue #2196).
70
+ */
71
+ const INLINE_FILLERS: Readonly<Partial<Record<ScriptHint, readonly string[]>>> = {
72
+ japanese: ["えーと", "えっと", "ええと", "あのー", "あのう", "うーん"],
73
+ han: ["呃", "嗯"],
74
+ };
75
+
76
+ /**
77
+ * Punctuation that trails a filler and collapses with it, so
78
+ * 「えーと、では」 becomes 「では」 rather than 「、では」. Arabic sentence
79
+ * punctuation is included: an Arabic filler is normally followed by
80
+ * `،`, and without it the token survives the pass.
81
+ */
82
+ const TRAILING_FILLER_PUNCTUATION = "[,.、。،؛؟!?]?";
83
+
84
+ interface FillerMatchers {
85
+ token: RegExp | null;
86
+ inline: RegExp | null;
87
+ }
88
+
89
+ /**
90
+ * Compiled matchers are cached per (script hints + extra tokens) key.
91
+ * A day of transcripts is thousands of segments over a handful of
92
+ * scripts, so this compiles a few regexes instead of one per segment.
93
+ */
94
+ const FILLER_MATCHER_CACHE = new Map<string, FillerMatchers>();
95
+
96
+ function escapeRegExp(value: string): string {
97
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
98
+ }
99
+
100
+ /**
101
+ * Build one alternation from `tokens`, or `null` when none remain.
102
+ *
103
+ * Each token contributes BOTH its composed and its decomposed spelling,
104
+ * so `ähm` also matches an ASR that emits `a\u0308hm`. Matching both
105
+ * forms keeps the surrounding transcript byte-identical, which
106
+ * normalizing the text before stripping would not.
107
+ *
108
+ * JavaScript alternation is first-match, not longest-match, so the
109
+ * tokens are sorted longest-first. Without that a built-in prefix such
110
+ * as 「あのー」 would match ahead of a longer operator token that starts
111
+ * with it and leave the tail in the transcript. Ties break on the token
112
+ * itself so the pattern is stable across runs.
113
+ */
114
+ function buildAlternation(
115
+ tokens: readonly string[],
116
+ shape: (pattern: string) => { source: string; flags: string },
117
+ ): RegExp | null {
118
+ const forms = new Set<string>();
119
+ for (const token of tokens) {
120
+ forms.add(token.normalize("NFC"));
121
+ forms.add(token.normalize("NFD"));
122
+ }
123
+ const unique = [...forms].sort(
124
+ (left, right) => right.length - left.length || (left < right ? -1 : left > right ? 1 : 0),
125
+ );
126
+ if (unique.length === 0) return null;
127
+ const { source, flags } = shape(unique.map((token) => escapeRegExp(token)).join("|"));
128
+ return new RegExp(source, flags);
129
+ }
130
+
131
+ function buildFillerMatchers(
132
+ hints: readonly ScriptHint[],
133
+ extraTokens: readonly string[],
134
+ ): FillerMatchers {
135
+ // Structural key: comma-joining collapses `["a,b"]` and `["a","b"]`
136
+ // onto one entry, and the first caller's regex would then clean the
137
+ // second caller's transcripts.
138
+ const key = JSON.stringify([hints, extraTokens]);
139
+ const cached = FILLER_MATCHER_CACHE.get(key);
140
+ if (cached) return cached;
141
+
142
+ const tokens: string[] = [];
143
+ const inline: string[] = [];
144
+ for (const hint of hints) {
145
+ tokens.push(...(TOKEN_FILLERS[hint] ?? []));
146
+ inline.push(...(INLINE_FILLERS[hint] ?? []));
147
+ }
148
+ // Operator tokens apply to every script: the operator knows their own
149
+ // language, and routing them by script hint would silently drop them.
150
+ for (const extra of extraTokens) {
151
+ const trimmed = extra.trim();
152
+ if (trimmed.length === 0) continue;
153
+ if (SPACE_DELIMITED_TOKEN.test(trimmed)) tokens.push(trimmed);
154
+ else inline.push(trimmed);
155
+ }
156
+
157
+ const matchers: FillerMatchers = {
158
+ token: buildAlternation(tokens, (pattern) => ({
159
+ source: `(?:^|\\s)(?:${pattern})${TRAILING_FILLER_PUNCTUATION}(?=\\s|$)`,
160
+ flags: "giu",
161
+ })),
162
+ inline: buildAlternation(inline, (pattern) => ({
163
+ source: `(?:${pattern})${TRAILING_FILLER_PUNCTUATION}`,
164
+ flags: "gu",
165
+ })),
166
+ };
167
+ FILLER_MATCHER_CACHE.set(key, matchers);
168
+ return matchers;
169
+ }
170
+
171
+ /**
172
+ * A token whose first character belongs to a space-delimited script.
173
+ *
174
+ * Anything outside this set is removed INLINE, so a script that does
175
+ * space its words must appear here. Hebrew `אה` routed to the inline
176
+ * matcher would strip the suffix of `נראה` and corrupt the transcript.
177
+ */
178
+ const SPACE_DELIMITED_TOKEN =
179
+ /^[\p{Script=Latin}\p{Script=Cyrillic}\p{Script=Greek}\p{Script=Hangul}\p{Script=Arabic}\p{Script=Hebrew}\p{N}]/u;
49
180
 
50
181
  /** Apply configured cleanup passes to one conversation. */
51
182
  export function cleanConversation(
52
183
  conversation: WearableConversation,
53
184
  settings: WearableCleanupSettings,
185
+ extraFillerTokens: readonly string[] = [],
54
186
  ): CleanupResult {
55
187
  let segments = conversation.segments.map((segment) => ({ ...segment }));
56
188
  let droppedSegments = 0;
@@ -61,7 +193,7 @@ export function cleanConversation(
61
193
 
62
194
  if (settings.stripFillers) {
63
195
  for (const segment of segments) {
64
- segment.text = stripFillerTokens(segment.text);
196
+ segment.text = stripFillerTokens(segment.text, extraFillerTokens);
65
197
  }
66
198
  }
67
199
 
@@ -130,15 +262,30 @@ function canMerge(
130
262
  const label = previous.speakerKey.trim();
131
263
  if (label.length === 0 || GENERIC_SPEAKER_PATTERN.test(label)) return false;
132
264
  }
133
- const previousEnd = previous.endIso ? Date.parse(previous.endIso) : NaN;
134
- const nextStart = next.startIso ? Date.parse(next.startIso) : NaN;
265
+ const previousEnd = previous.endIso
266
+ ? Date.parse(previous.endIso)
267
+ : Number.NaN;
268
+ const nextStart = next.startIso ? Date.parse(next.startIso) : Number.NaN;
135
269
  // Without timestamps, adjacency is the only signal — still merge.
136
270
  if (Number.isNaN(previousEnd) || Number.isNaN(nextStart)) return true;
137
271
  return nextStart - previousEnd <= MERGE_GAP_MS;
138
272
  }
139
273
 
140
- export function stripFillerTokens(text: string): string {
141
- return normalizeWhitespace(text.replace(FILLER_PATTERN, " "));
274
+ /**
275
+ * Remove filler tokens from `text`. The built-in token set is chosen
276
+ * from the scripts present in the text, so a Japanese or Korean segment
277
+ * is cleaned by its own list instead of silently keeping every filler
278
+ * (issue #2196). Operator tokens apply to every script.
279
+ */
280
+ export function stripFillerTokens(
281
+ text: string,
282
+ extraTokens: readonly string[] = [],
283
+ ): string {
284
+ const matchers = buildFillerMatchers(detectScriptHints(text), extraTokens);
285
+ let result = text;
286
+ if (matchers.token) result = result.replace(matchers.token, " ");
287
+ if (matchers.inline) result = result.replace(matchers.inline, "");
288
+ return normalizeWhitespace(result);
142
289
  }
143
290
 
144
291
  /**