@remnic/core 9.5.0 → 9.6.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 (296) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +12 -12
  3. package/dist/access-boundary.d.ts +6 -6
  4. package/dist/access-boundary.js +13 -13
  5. package/dist/access-cli.js +36 -36
  6. package/dist/access-http.d.ts +6 -6
  7. package/dist/access-http.js +17 -17
  8. package/dist/access-identity-continuity-surface.d.ts +5 -5
  9. package/dist/access-identity-continuity-surface.js +12 -12
  10. package/dist/access-lcm-surface.d.ts +6 -6
  11. package/dist/access-lcm-surface.js +12 -12
  12. package/dist/access-mcp.d.ts +6 -6
  13. package/dist/access-mcp.js +16 -16
  14. package/dist/access-observe-write-surface.d.ts +6 -6
  15. package/dist/access-observe-write-surface.js +12 -12
  16. package/dist/access-operations-batch.js +14 -14
  17. package/dist/access-operations.d.ts +7 -7
  18. package/dist/access-operations.js +15 -15
  19. package/dist/access-recall-surface.d.ts +6 -6
  20. package/dist/access-recall-surface.js +12 -12
  21. package/dist/access-schema.d.ts +4 -4
  22. package/dist/{access-service-DZX0sYAv.d.ts → access-service-CXJaql3A.d.ts} +4 -4
  23. package/dist/access-service.d.ts +6 -6
  24. package/dist/access-service.js +12 -12
  25. package/dist/access-surface-catalog.d.ts +6 -6
  26. package/dist/action-confidence.d.ts +1 -1
  27. package/dist/active-memory-bridge.d.ts +1 -1
  28. package/dist/active-recall.d.ts +1 -1
  29. package/dist/active-recall.js +1 -1
  30. package/dist/{auto-sync-G6IU7L6C.js → auto-sync-4A5YBWYO.js} +3 -3
  31. package/dist/behavior-learner.d.ts +1 -1
  32. package/dist/behavior-signals.d.ts +1 -1
  33. package/dist/bootstrap.d.ts +5 -5
  34. package/dist/briefing.d.ts +2 -2
  35. package/dist/briefing.js +4 -4
  36. package/dist/buffer-surprise-report.d.ts +1 -1
  37. package/dist/buffer.d.ts +2 -2
  38. package/dist/calibration.d.ts +1 -1
  39. package/dist/capabilities.d.ts +1 -1
  40. package/dist/{catalog-DoH2b0OR.d.ts → catalog-C9cu67Ig.d.ts} +1 -1
  41. package/dist/causal-behavior.d.ts +1 -1
  42. package/dist/causal-consolidation.d.ts +1 -1
  43. package/dist/causal-consolidation.js +5 -5
  44. package/dist/causal-trajectory-graph.d.ts +1 -1
  45. package/dist/{chunk-H5PFR5ZT.js → chunk-23JFRB73.js} +2 -2
  46. package/dist/{chunk-BGMSRN6I.js → chunk-3XIY7MDQ.js} +3 -3
  47. package/dist/{chunk-L2QJQSHL.js → chunk-4GNEDXDI.js} +2 -2
  48. package/dist/{chunk-MFDJ63U5.js → chunk-4LV23CHK.js} +2 -2
  49. package/dist/{chunk-AER6MT24.js → chunk-6SXVCD7W.js} +2 -2
  50. package/dist/{chunk-2SFEFR5I.js → chunk-72WG5QKN.js} +2 -2
  51. package/dist/{chunk-7BAJAXGB.js → chunk-7MW3CVLD.js} +2 -2
  52. package/dist/{chunk-SNNIUDRS.js → chunk-AYZJID4S.js} +2 -2
  53. package/dist/{chunk-M7XQSUBB.js → chunk-B3ABVQ36.js} +116 -9
  54. package/dist/chunk-B3ABVQ36.js.map +1 -0
  55. package/dist/{chunk-LNHJ32NK.js → chunk-BEM4D2QG.js} +3 -3
  56. package/dist/{chunk-AOYJVCFN.js → chunk-CYWA2CXR.js} +2 -2
  57. package/dist/{chunk-2JFDHI2K.js → chunk-DEG5ULFJ.js} +2 -2
  58. package/dist/{chunk-L6V3WJ55.js → chunk-DS7MM2ES.js} +3 -3
  59. package/dist/{chunk-CXEL3Y22.js → chunk-FKCRGLRD.js} +184 -29
  60. package/dist/{chunk-CXEL3Y22.js.map → chunk-FKCRGLRD.js.map} +1 -1
  61. package/dist/{chunk-TI7IHDBQ.js → chunk-H2DXDQCQ.js} +5 -5
  62. package/dist/{chunk-KMF67QDQ.js → chunk-HLN7RROI.js} +4 -4
  63. package/dist/{chunk-D2XYYU5Q.js → chunk-J3XHPFEU.js} +2 -2
  64. package/dist/{chunk-ODSYJXDU.js → chunk-JSNVWNGX.js} +2 -2
  65. package/dist/{chunk-7Z2DZNWN.js → chunk-KL4UAXUY.js} +43 -2
  66. package/dist/{chunk-7Z2DZNWN.js.map → chunk-KL4UAXUY.js.map} +1 -1
  67. package/dist/{chunk-4WNLSSHL.js → chunk-LADFWYHY.js} +4 -4
  68. package/dist/{chunk-UBGAOYBP.js → chunk-LR5DZ56F.js} +2 -2
  69. package/dist/{chunk-5K5VEDFY.js → chunk-N3JDIRBW.js} +2 -2
  70. package/dist/{chunk-2B7DP7AP.js → chunk-P454KXD6.js} +93 -15
  71. package/dist/chunk-P454KXD6.js.map +1 -0
  72. package/dist/{chunk-VWEWK2BM.js → chunk-PCLIWBGD.js} +3 -3
  73. package/dist/{chunk-TV7C3HCB.js → chunk-PNLFN2PZ.js} +2 -2
  74. package/dist/{chunk-STCCFQU5.js → chunk-PVUJ6WVV.js} +6 -6
  75. package/dist/{chunk-ITWTKT2R.js → chunk-RGK27ELN.js} +2 -2
  76. package/dist/{chunk-II6WHCCW.js → chunk-SIRPLF54.js} +2 -2
  77. package/dist/{chunk-UDCDGP6A.js → chunk-SVOHGUMI.js} +1188 -112
  78. package/dist/chunk-SVOHGUMI.js.map +1 -0
  79. package/dist/{chunk-NFTABV56.js → chunk-TLUDFPZL.js} +2 -2
  80. package/dist/{chunk-I74ZEBMA.js → chunk-UFUKHGZD.js} +4 -4
  81. package/dist/{chunk-L4UZBJNM.js → chunk-UTRJFDP2.js} +2 -2
  82. package/dist/{chunk-5GPPACXK.js → chunk-VSMRDBFT.js} +2 -1
  83. package/dist/{chunk-SNBEVJBX.js → chunk-XCL57M6Q.js} +3 -3
  84. package/dist/{chunk-DZSAXE3T.js → chunk-Y7UNNAZD.js} +2 -2
  85. package/dist/{chunk-XN3TUUX2.js → chunk-YQB32INI.js} +2 -2
  86. package/dist/{cli-1dghPCO2.d.ts → cli-CqdPA6z9.d.ts} +3 -3
  87. package/dist/cli.d.ts +7 -7
  88. package/dist/cli.js +29 -29
  89. package/dist/compounding/engine.d.ts +2 -2
  90. package/dist/compounding/engine.js +4 -4
  91. package/dist/compounding/preference-consolidator.d.ts +1 -1
  92. package/dist/compression-optimizer.d.ts +1 -1
  93. package/dist/config.d.ts +1 -1
  94. package/dist/config.js +1 -1
  95. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  96. package/dist/connectors/codex-materialize-runner.js +4 -4
  97. package/dist/connectors/codex-materialize.d.ts +1 -1
  98. package/dist/connectors/index.d.ts +1 -1
  99. package/dist/connectors/index.js +4 -4
  100. package/dist/consolidation-provenance-check.d.ts +2 -2
  101. package/dist/consolidation-undo.d.ts +2 -2
  102. package/dist/contradiction/index.d.ts +2 -2
  103. package/dist/conversation-index/backend.d.ts +1 -1
  104. package/dist/conversation-index/chunker.d.ts +1 -1
  105. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  106. package/dist/conversation-index/indexer.d.ts +1 -1
  107. package/dist/conversation-index/search.d.ts +1 -1
  108. package/dist/day-summary.d.ts +1 -1
  109. package/dist/delinearize.d.ts +1 -1
  110. package/dist/direct-answer-wiring.d.ts +1 -1
  111. package/dist/direct-answer.d.ts +1 -1
  112. package/dist/embedding-fallback.d.ts +1 -1
  113. package/dist/enrichment/index.d.ts +1 -1
  114. package/dist/entity-retrieval.d.ts +2 -2
  115. package/dist/entity-retrieval.js +4 -4
  116. package/dist/entity-schema.d.ts +1 -1
  117. package/dist/explicit-capture.d.ts +5 -5
  118. package/dist/extraction-faithfulness.d.ts +1 -1
  119. package/dist/extraction-judge-telemetry.d.ts +1 -1
  120. package/dist/extraction-judge-training.d.ts +1 -1
  121. package/dist/extraction-judge.d.ts +1 -1
  122. package/dist/extraction.d.ts +1 -1
  123. package/dist/fallback-llm.d.ts +1 -1
  124. package/dist/graph-dashboard-diff.d.ts +1 -1
  125. package/dist/graph-dashboard-key.d.ts +1 -1
  126. package/dist/graph-dashboard-parser.d.ts +1 -1
  127. package/dist/graph-edge-reinforcement.d.ts +1 -1
  128. package/dist/graph-snapshot.d.ts +1 -1
  129. package/dist/graph.d.ts +1 -1
  130. package/dist/identity-continuity.d.ts +1 -1
  131. package/dist/importance.d.ts +1 -1
  132. package/dist/index.d.ts +268 -17
  133. package/dist/index.js +80 -38
  134. package/dist/intent.d.ts +1 -1
  135. package/dist/lcm/engine.d.ts +1 -1
  136. package/dist/lcm/index.d.ts +1 -1
  137. package/dist/lcm/tools.d.ts +1 -1
  138. package/dist/lifecycle.d.ts +1 -1
  139. package/dist/live-connectors-runner.d.ts +1 -1
  140. package/dist/local-llm.d.ts +1 -1
  141. package/dist/local-model-endpoint.d.ts +1 -1
  142. package/dist/maintenance/memory-governance.d.ts +1 -1
  143. package/dist/maintenance/memory-governance.js +4 -4
  144. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  145. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  146. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  147. package/dist/memory-action-policy.d.ts +1 -1
  148. package/dist/memory-cache.d.ts +1 -1
  149. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  150. package/dist/memory-projection-store.d.ts +1 -1
  151. package/dist/memory-provenance.d.ts +1 -1
  152. package/dist/memory-worth-outcomes.d.ts +2 -2
  153. package/dist/models-json.d.ts +1 -1
  154. package/dist/namespaces/migrate.d.ts +3 -3
  155. package/dist/namespaces/migrate.js +11 -11
  156. package/dist/namespaces/principal.d.ts +1 -1
  157. package/dist/namespaces/search.d.ts +1 -1
  158. package/dist/namespaces/search.js +7 -7
  159. package/dist/namespaces/storage.d.ts +3 -3
  160. package/dist/namespaces/storage.js +4 -4
  161. package/dist/native-knowledge.d.ts +1 -1
  162. package/dist/operator-toolkit.d.ts +2 -2
  163. package/dist/operator-toolkit.js +15 -15
  164. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  165. package/dist/orchestration/maintenance.d.ts +2 -2
  166. package/dist/orchestration/maintenance.js +6 -6
  167. package/dist/{orchestrator-Dh3WSW_O.d.ts → orchestrator-CWFrT8AF.d.ts} +91 -60
  168. package/dist/orchestrator.d.ts +5 -5
  169. package/dist/orchestrator.js +36 -36
  170. package/dist/patterns-cli.d.ts +1 -1
  171. package/dist/policy-runtime.d.ts +1 -1
  172. package/dist/provenance.d.ts +1 -1
  173. package/dist/{qmd-DoUVxqND.d.ts → qmd--fnPMK60.d.ts} +1 -1
  174. package/dist/qmd-preflight.d.ts +2 -2
  175. package/dist/qmd-recall-cache.d.ts +1 -1
  176. package/dist/qmd.d.ts +2 -2
  177. package/dist/recall-disclosure-escalation.d.ts +1 -1
  178. package/dist/recall-explain-renderer.d.ts +1 -1
  179. package/dist/recall-planner-llm.d.ts +1 -1
  180. package/dist/recall-state.d.ts +1 -1
  181. package/dist/recall-tag-filter.d.ts +1 -1
  182. package/dist/recall-timings.d.ts +1 -1
  183. package/dist/recall-xray-cli.d.ts +1 -1
  184. package/dist/recall-xray-renderer.d.ts +1 -1
  185. package/dist/recall-xray.d.ts +1 -1
  186. package/dist/resolve-auth-token.d.ts +1 -1
  187. package/dist/resume-bundles.js +2 -2
  188. package/dist/retrieval-agents.d.ts +2 -2
  189. package/dist/retrieval-tiers.d.ts +1 -1
  190. package/dist/routing/engine.d.ts +1 -1
  191. package/dist/routing/store.d.ts +1 -1
  192. package/dist/schemas.d.ts +22 -22
  193. package/dist/search/document-scanner.js +2 -2
  194. package/dist/search/embed-helper.d.ts +1 -1
  195. package/dist/search/factory.d.ts +1 -1
  196. package/dist/search/factory.js +6 -6
  197. package/dist/search/index.d.ts +1 -1
  198. package/dist/search/index.js +6 -6
  199. package/dist/search/lancedb-backend.d.ts +1 -1
  200. package/dist/search/lancedb-backend.js +3 -3
  201. package/dist/search/meilisearch-backend.d.ts +1 -1
  202. package/dist/search/meilisearch-backend.js +3 -3
  203. package/dist/search/noop-backend.d.ts +1 -1
  204. package/dist/search/orama-backend.d.ts +1 -1
  205. package/dist/search/orama-backend.js +3 -3
  206. package/dist/search/port.d.ts +1 -1
  207. package/dist/search/remote-backend.d.ts +1 -1
  208. package/dist/{semantic-consolidation-Dof1865o.d.ts → semantic-consolidation-O5lMZ4LG.d.ts} +1 -1
  209. package/dist/semantic-consolidation.d.ts +2 -2
  210. package/dist/semantic-consolidation.js +5 -5
  211. package/dist/semantic-rule-promotion.js +4 -4
  212. package/dist/semantic-rule-verifier.d.ts +1 -1
  213. package/dist/semantic-rule-verifier.js +4 -4
  214. package/dist/session-observer-bands.d.ts +1 -1
  215. package/dist/session-observer-state.d.ts +1 -1
  216. package/dist/shared-context/manager.d.ts +1 -1
  217. package/dist/signal.d.ts +1 -1
  218. package/dist/storage-C1maCNbc.d.ts +1591 -0
  219. package/dist/storage.d.ts +5 -1239
  220. package/dist/storage.js +3 -3
  221. package/dist/summarizer.d.ts +1 -1
  222. package/dist/summary-snapshot.d.ts +1 -1
  223. package/dist/temporal-supersession.d.ts +2 -2
  224. package/dist/temporal-validity.d.ts +1 -1
  225. package/dist/threading.d.ts +1 -1
  226. package/dist/tier-migration.d.ts +2 -2
  227. package/dist/tier-routing.d.ts +1 -1
  228. package/dist/topics.d.ts +1 -1
  229. package/dist/transcript.d.ts +1 -1
  230. package/dist/transfer/types.d.ts +12 -12
  231. package/dist/trust-score-stage.d.ts +1 -1
  232. package/dist/trust-score.d.ts +1 -1
  233. package/dist/{types-C7HUgOuA.d.ts → types-CGgcNWCG.d.ts} +31 -1
  234. package/dist/types.d.ts +1 -1
  235. package/dist/utility-runtime.d.ts +1 -1
  236. package/dist/verified-recall.js +4 -4
  237. package/package.json +2 -2
  238. package/src/cli-wearables-fuse.test.ts +197 -0
  239. package/src/cli.ts +25 -0
  240. package/src/storage.ts +16 -0
  241. package/src/wearables/cli.test.ts +102 -0
  242. package/src/wearables/cli.ts +65 -0
  243. package/src/wearables/config.test.ts +32 -0
  244. package/src/wearables/config.ts +52 -0
  245. package/src/wearables/day-store.test.ts +246 -0
  246. package/src/wearables/day-store.ts +272 -3
  247. package/src/wearables/fusion/cluster.ts +230 -0
  248. package/src/wearables/fusion/fuse.ts +218 -0
  249. package/src/wearables/fusion/fusion.test.ts +2575 -0
  250. package/src/wearables/fusion/index.ts +50 -0
  251. package/src/wearables/fusion/reconcile.ts +831 -0
  252. package/src/wearables/fusion/reconstruct.ts +251 -0
  253. package/src/wearables/fusion/store.ts +480 -0
  254. package/src/wearables/fusion/types.ts +228 -0
  255. package/src/wearables/index.ts +41 -0
  256. package/src/wearables/service.test.ts +1030 -2
  257. package/src/wearables/service.ts +258 -16
  258. package/src/wearables/speakers.test.ts +128 -0
  259. package/src/wearables/speakers.ts +58 -4
  260. package/src/wearables/storage-io.test.ts +146 -0
  261. package/src/wearables/types.ts +31 -0
  262. package/dist/chunk-2B7DP7AP.js.map +0 -1
  263. package/dist/chunk-M7XQSUBB.js.map +0 -1
  264. package/dist/chunk-UDCDGP6A.js.map +0 -1
  265. /package/dist/{auto-sync-G6IU7L6C.js.map → auto-sync-4A5YBWYO.js.map} +0 -0
  266. /package/dist/{chunk-H5PFR5ZT.js.map → chunk-23JFRB73.js.map} +0 -0
  267. /package/dist/{chunk-BGMSRN6I.js.map → chunk-3XIY7MDQ.js.map} +0 -0
  268. /package/dist/{chunk-L2QJQSHL.js.map → chunk-4GNEDXDI.js.map} +0 -0
  269. /package/dist/{chunk-MFDJ63U5.js.map → chunk-4LV23CHK.js.map} +0 -0
  270. /package/dist/{chunk-AER6MT24.js.map → chunk-6SXVCD7W.js.map} +0 -0
  271. /package/dist/{chunk-2SFEFR5I.js.map → chunk-72WG5QKN.js.map} +0 -0
  272. /package/dist/{chunk-7BAJAXGB.js.map → chunk-7MW3CVLD.js.map} +0 -0
  273. /package/dist/{chunk-SNNIUDRS.js.map → chunk-AYZJID4S.js.map} +0 -0
  274. /package/dist/{chunk-LNHJ32NK.js.map → chunk-BEM4D2QG.js.map} +0 -0
  275. /package/dist/{chunk-AOYJVCFN.js.map → chunk-CYWA2CXR.js.map} +0 -0
  276. /package/dist/{chunk-2JFDHI2K.js.map → chunk-DEG5ULFJ.js.map} +0 -0
  277. /package/dist/{chunk-L6V3WJ55.js.map → chunk-DS7MM2ES.js.map} +0 -0
  278. /package/dist/{chunk-TI7IHDBQ.js.map → chunk-H2DXDQCQ.js.map} +0 -0
  279. /package/dist/{chunk-KMF67QDQ.js.map → chunk-HLN7RROI.js.map} +0 -0
  280. /package/dist/{chunk-D2XYYU5Q.js.map → chunk-J3XHPFEU.js.map} +0 -0
  281. /package/dist/{chunk-ODSYJXDU.js.map → chunk-JSNVWNGX.js.map} +0 -0
  282. /package/dist/{chunk-4WNLSSHL.js.map → chunk-LADFWYHY.js.map} +0 -0
  283. /package/dist/{chunk-UBGAOYBP.js.map → chunk-LR5DZ56F.js.map} +0 -0
  284. /package/dist/{chunk-5K5VEDFY.js.map → chunk-N3JDIRBW.js.map} +0 -0
  285. /package/dist/{chunk-VWEWK2BM.js.map → chunk-PCLIWBGD.js.map} +0 -0
  286. /package/dist/{chunk-TV7C3HCB.js.map → chunk-PNLFN2PZ.js.map} +0 -0
  287. /package/dist/{chunk-STCCFQU5.js.map → chunk-PVUJ6WVV.js.map} +0 -0
  288. /package/dist/{chunk-ITWTKT2R.js.map → chunk-RGK27ELN.js.map} +0 -0
  289. /package/dist/{chunk-II6WHCCW.js.map → chunk-SIRPLF54.js.map} +0 -0
  290. /package/dist/{chunk-NFTABV56.js.map → chunk-TLUDFPZL.js.map} +0 -0
  291. /package/dist/{chunk-I74ZEBMA.js.map → chunk-UFUKHGZD.js.map} +0 -0
  292. /package/dist/{chunk-L4UZBJNM.js.map → chunk-UTRJFDP2.js.map} +0 -0
  293. /package/dist/{chunk-5GPPACXK.js.map → chunk-VSMRDBFT.js.map} +0 -0
  294. /package/dist/{chunk-SNBEVJBX.js.map → chunk-XCL57M6Q.js.map} +0 -0
  295. /package/dist/{chunk-DZSAXE3T.js.map → chunk-Y7UNNAZD.js.map} +0 -0
  296. /package/dist/{chunk-XN3TUUX2.js.map → chunk-YQB32INI.js.map} +0 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Wearable cross-source fusion — day-level orchestrator.
3
+ *
4
+ * Takes every enabled source's normalized conversations for one day,
5
+ * clusters them across sources, reconciles each cluster, and attaches a
6
+ * stable content-hash id so re-running over unchanged inputs produces a
7
+ * byte-identical artifact (idempotent — no duplicate files).
8
+ *
9
+ * Deterministic end to end: no timestamps from "now", no LLM, no
10
+ * randomness. The only time-derived value is the `fusedAt` frontmatter
11
+ * field added at storage time (excluded from the content hash).
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+ import { clusterConversations } from "./cluster.js";
16
+ import {
17
+ DEFAULT_SOURCE_TRUST,
18
+ DEFAULT_WINDOW_TOLERANCE_MS,
19
+ fuseCluster,
20
+ type FuseClusterResult,
21
+ } from "./reconcile.js";
22
+ import { DEFAULT_PROXIMITY_GAP_MS } from "./cluster.js";
23
+ import type {
24
+ FusionConversationInput,
25
+ FusionDayResult,
26
+ FusionOptions,
27
+ FusedConversationProvenance,
28
+ FusedWearableConversation,
29
+ } from "./types.js";
30
+
31
+ const FUSION_ID_PREFIX = "fusion";
32
+ /**
33
+ * Fusion algorithm/schema version. Folded into the day content hash (the
34
+ * idempotency key) so a change to the clustering, reconciliation, or
35
+ * reconstruction algorithm invalidates cached artifacts even when the raw
36
+ * inputs and fusion config are byte-identical — pre-existing `_fusion/`
37
+ * files are regenerated rather than served stale. Bump this whenever the
38
+ * fusion algorithm meaningfully changes (this PR changed it repeatedly).
39
+ * The per-conversation id (`canonicalInputsKey`) is intentionally NOT
40
+ * folded with the version, so a conversation keeps a stable identity.
41
+ */
42
+ export const FUSION_ALGO_VERSION = "2026-07-12-1";
43
+
44
+ /**
45
+ * Canonical, stable serialization of a set of fusion inputs — the base
46
+ * for the per-conversation id. Input-only so a conversation keeps a
47
+ * stable identity across re-runs regardless of input order. The day
48
+ * content hash additionally folds the effective fusion config (see
49
+ * `canonicalDayKey`) so a config change invalidates the cached artifact.
50
+ */
51
+ function canonicalInputsKey(
52
+ date: string,
53
+ inputs: readonly FusionConversationInput[],
54
+ ): string {
55
+ const sorted = [...inputs].sort((a, b) => {
56
+ if (a.source !== b.source) return a.source < b.source ? -1 : 1;
57
+ if (a.conversationId !== b.conversationId) {
58
+ return a.conversationId < b.conversationId ? -1 : 1;
59
+ }
60
+ return 0;
61
+ });
62
+ // Serialize the structured tuples UNAMBIGUOUSLY before hashing. The old
63
+ // `|`/newline-delimited form could collide: a speaker label or segment
64
+ // text containing `|` (or a newline plus a forged `seg|…`/`conv|…`
65
+ // prefix) could make two DISTINCT inputs serialize to identical bytes,
66
+ // hashing alike — so fuseDay wrongly took the idempotent-skip path and
67
+ // kept serving a stale artifact. JSON string quoting makes every field
68
+ // boundary unforgeable, and the positional array order is the
69
+ // deterministic key order (no object keys to sort).
70
+ const record = [
71
+ date,
72
+ sorted.map((conv) => [
73
+ conv.source,
74
+ conv.conversationId,
75
+ conv.startIso,
76
+ conv.endIso ?? "",
77
+ conv.title ?? "",
78
+ conv.summary ?? "",
79
+ conv.segments.map((segment) => [
80
+ segment.speaker,
81
+ segment.isSelf ? "self" : "other",
82
+ segment.startIso ?? "",
83
+ segment.endIso ?? "",
84
+ segment.text,
85
+ ]),
86
+ ]),
87
+ ];
88
+ return createHash("sha256").update(JSON.stringify(record), "utf-8").digest("hex");
89
+ }
90
+
91
+ /**
92
+ * Effective-config fingerprint folded into the day content hash so a
93
+ * change to any clustering/reconciliation knob (proximity gap, window
94
+ * tolerance, or per-source trust) invalidates the cached artifact.
95
+ * Defaults are resolved so an explicit default hashes identically to an
96
+ * omission, keeping the skip-unchanged path deterministic. Only sources
97
+ * that actually contribute to the day are included, so a trust change
98
+ * for an absent source cannot spuriously trigger a rebuild.
99
+ */
100
+ function configFingerprint(
101
+ options: FusionOptions,
102
+ sources: readonly string[],
103
+ ): string {
104
+ const proximityGapMs = options.proximityGapMs ?? DEFAULT_PROXIMITY_GAP_MS;
105
+ const windowToleranceMs =
106
+ options.windowToleranceMs ?? DEFAULT_WINDOW_TOLERANCE_MS;
107
+ const trustMap = options.sourceTrust ?? {};
108
+ // JSON form so a source id (or future field) containing `|` cannot forge
109
+ // a trust-entry boundary and collide two distinct effective configs into
110
+ // one day hash.
111
+ const trustEntries = [...new Set(sources)]
112
+ .sort()
113
+ .map((source) => [source, trustMap[source] ?? DEFAULT_SOURCE_TRUST]);
114
+ return JSON.stringify([proximityGapMs, windowToleranceMs, trustEntries]);
115
+ }
116
+
117
+ /**
118
+ * Day idempotency key: the input-only hash combined with the effective
119
+ * fusion-config fingerprint AND the fusion algorithm version. Same inputs +
120
+ * same config + same version => same hash (no duplicate rewrite); a config
121
+ * change OR an algorithm-version bump => new hash => rebuild. The version
122
+ * is what forces regeneration when the ALGORITHM itself changes but the
123
+ * inputs and config are byte-identical (issue #1849).
124
+ */
125
+ export function canonicalDayKey(
126
+ date: string,
127
+ inputs: readonly FusionConversationInput[],
128
+ options: FusionOptions,
129
+ algoVersion: string = FUSION_ALGO_VERSION,
130
+ ): string {
131
+ const inputsKey = canonicalInputsKey(date, inputs);
132
+ const fingerprint = configFingerprint(
133
+ options,
134
+ inputs.map((conv) => conv.source),
135
+ );
136
+ return createHash("sha256")
137
+ .update(inputsKey)
138
+ .update("\n")
139
+ .update(fingerprint)
140
+ .update("\n")
141
+ .update(algoVersion)
142
+ .digest("hex");
143
+ }
144
+
145
+ function buildProvenance(
146
+ cluster: readonly FusionConversationInput[],
147
+ proximityGapMs: number,
148
+ windowToleranceMs: number,
149
+ ): FusedConversationProvenance {
150
+ return {
151
+ contributions: cluster.map((conv) => ({
152
+ source: conv.source,
153
+ conversationId: conv.conversationId,
154
+ startIso: conv.startIso,
155
+ ...(conv.endIso !== undefined ? { endIso: conv.endIso } : {}),
156
+ segmentCount: conv.segments.length,
157
+ })),
158
+ proximityGapMs,
159
+ windowToleranceMs,
160
+ method: "time-proximity",
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Fuse every source's conversations for one day into a stable result.
166
+ * Empty input yields an empty (but well-formed) result so callers can
167
+ * treat "nothing to fuse" uniformly.
168
+ */
169
+ export function fuseDay(
170
+ date: string,
171
+ inputs: readonly FusionConversationInput[],
172
+ options: FusionOptions = {},
173
+ ): FusionDayResult {
174
+ const proximityGapMs = options.proximityGapMs ?? DEFAULT_PROXIMITY_GAP_MS;
175
+ const windowToleranceMs =
176
+ options.windowToleranceMs ?? DEFAULT_WINDOW_TOLERANCE_MS;
177
+
178
+ const clusters = clusterConversations(inputs, proximityGapMs);
179
+ const conversations: FusedWearableConversation[] = clusters.map((cluster) => {
180
+ const fused: FuseClusterResult = fuseCluster(cluster, {
181
+ ...options,
182
+ windowToleranceMs,
183
+ });
184
+ const id = `${FUSION_ID_PREFIX}-${canonicalInputsKey(date, cluster).slice(0, 24)}`;
185
+ const result: FusedWearableConversation = {
186
+ id,
187
+ date,
188
+ startIso: fused.startIso,
189
+ sources: fused.sources,
190
+ speakers: fused.speakers,
191
+ segments: fused.segments,
192
+ disagreements: fused.disagreements,
193
+ provenance: buildProvenance(cluster, proximityGapMs, windowToleranceMs),
194
+ };
195
+ if (fused.endIso !== undefined) result.endIso = fused.endIso;
196
+ if (fused.title !== undefined) result.title = fused.title;
197
+ if (fused.summary !== undefined) result.summary = fused.summary;
198
+ return result;
199
+ });
200
+
201
+ const sourcesSeen = new Set<string>();
202
+ const sources: string[] = [];
203
+ for (const conv of conversations) {
204
+ for (const source of conv.sources) {
205
+ if (!sourcesSeen.has(source)) {
206
+ sourcesSeen.add(source);
207
+ sources.push(source);
208
+ }
209
+ }
210
+ }
211
+
212
+ return {
213
+ date,
214
+ conversations,
215
+ sources,
216
+ contentHash: canonicalDayKey(date, inputs, options),
217
+ };
218
+ }