@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
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/wearables/cleanup.ts","../src/wearables/sync-state.ts","../src/wearables/trust.ts","../src/wearables/memory-gen.ts","../src/wearables/pipeline.ts"],"sourcesContent":["/**\n * Wearable transcript cleanup — deterministic, zero-LLM normalization.\n *\n * ASR output from always-on wearables is noisy: fragmented utterances,\n * filler tokens, stuttered repeats, and occasional pure garbage. This\n * module cleans a conversation in place-order without changing meaning:\n * everything here is conservative and reversible by re-syncing.\n */\n\nimport type {\n WearableCleanupSettings,\n WearableConversation,\n WearableTranscriptSegment,\n} from \"./types.js\";\n\nexport interface CleanupResult {\n conversation: WearableConversation;\n /** Segments removed by the low-quality heuristic. */\n droppedSegments: number;\n /** Segments merged into a predecessor. */\n mergedSegments: number;\n}\n\n/** Merge consecutive same-speaker segments when gaps are below this. */\nconst MERGE_GAP_MS = 30_000;\n\n/**\n * Speaker labels that carry no real diarization signal. Bee (and other\n * providers that fall back to a placeholder) emit \"Unknown\"/\"unknown\"\n * or an empty string when diarization is unavailable; merging on such a\n * label would collapse an entire conversation into one segment (issue #1811).\n */\nconst GENERIC_SPEAKER_PATTERN = /^unknown$/i;\n\n/**\n * Standalone filler tokens stripped when `stripFillers` is on. Matched\n * case-insensitively on word boundaries, only as whole tokens — \"um\"\n * inside \"umbrella\" is never touched. Deliberately short, low-risk\n * list; meaning-bearing hedges (\"like\", \"well\") are NOT stripped.\n */\nconst FILLER_TOKENS = [\"um\", \"uh\", \"uhm\", \"umm\", \"uhh\", \"erm\", \"hmm\", \"mhm\"];\n\nconst FILLER_PATTERN = new RegExp(\n // Leading/trailing punctuation around the filler collapses with it so\n // \"Um, so we should\" -> \"so we should\" rather than \", so we should\".\n `(?:^|\\\\s)(?:${FILLER_TOKENS.join(\"|\")})[,.]?(?=\\\\s|$)`,\n \"gi\",\n);\n\n/** Apply configured cleanup passes to one conversation. */\nexport function cleanConversation(\n conversation: WearableConversation,\n settings: WearableCleanupSettings,\n): CleanupResult {\n let segments = conversation.segments.map((segment) => ({ ...segment }));\n let droppedSegments = 0;\n let mergedSegments = 0;\n // Optional on the type so other connectors keep the legacy no-op\n // default; absent means \"off\" (unchanged merge behavior).\n const preserveBoundaries = settings.preserveUtteranceBoundaries === true;\n\n if (settings.stripFillers) {\n for (const segment of segments) {\n segment.text = stripFillerTokens(segment.text);\n }\n }\n\n if (settings.collapseRepeats) {\n for (const segment of segments) {\n segment.text = collapseImmediateRepeats(segment.text);\n }\n }\n\n for (const segment of segments) {\n segment.text = normalizeWhitespace(segment.text);\n }\n\n if (settings.dropLowQuality) {\n const kept: WearableTranscriptSegment[] = [];\n for (const segment of segments) {\n if (isLowQualitySegment(segment.text)) {\n droppedSegments += 1;\n } else {\n kept.push(segment);\n }\n }\n segments = kept;\n } else {\n // Even without the quality heuristic, segments whose text became\n // empty after filler stripping carry no information.\n const kept = segments.filter((segment) => segment.text.length > 0);\n droppedSegments += segments.length - kept.length;\n segments = kept;\n }\n\n if (settings.mergeSameSpeaker) {\n const merged: WearableTranscriptSegment[] = [];\n for (const segment of segments) {\n const previous = merged[merged.length - 1];\n if (previous && canMerge(previous, segment, preserveBoundaries)) {\n previous.text = `${previous.text} ${segment.text}`.trim();\n if (segment.endIso) previous.endIso = segment.endIso;\n mergedSegments += 1;\n } else {\n merged.push(segment);\n }\n }\n segments = merged;\n }\n\n return {\n conversation: { ...conversation, segments },\n droppedSegments,\n mergedSegments,\n };\n}\n\nfunction canMerge(\n previous: WearableTranscriptSegment,\n next: WearableTranscriptSegment,\n preserveUtteranceBoundaries: boolean,\n): boolean {\n if (previous.speakerKey !== next.speakerKey) return false;\n // A generic/low-confidence speaker label (\"Unknown\", empty) carries no\n // real diarization: merging on it collapses unrelated utterances. When\n // the source opts into boundary preservation, refuse to merge across\n // such a label (issue #1811). The keys are equal here, so checking one\n // suffices; diarized labels still merge normally.\n if (preserveUtteranceBoundaries) {\n const label = previous.speakerKey.trim();\n if (label.length === 0 || GENERIC_SPEAKER_PATTERN.test(label)) return false;\n }\n const previousEnd = previous.endIso ? Date.parse(previous.endIso) : NaN;\n const nextStart = next.startIso ? Date.parse(next.startIso) : NaN;\n // Without timestamps, adjacency is the only signal — still merge.\n if (Number.isNaN(previousEnd) || Number.isNaN(nextStart)) return true;\n return nextStart - previousEnd <= MERGE_GAP_MS;\n}\n\nexport function stripFillerTokens(text: string): string {\n return normalizeWhitespace(text.replace(FILLER_PATTERN, \" \"));\n}\n\n/**\n * Collapse immediate word/phrase stutters: \"I I I think\" -> \"I think\",\n * \"we should we should go\" -> \"we should go\". Only collapses *adjacent*\n * repeats (up to 4-word phrases) so intentional repetition across a\n * sentence is preserved.\n */\nexport function collapseImmediateRepeats(text: string): string {\n const words = text.split(/\\s+/).filter((word) => word.length > 0);\n if (words.length < 2) return text.trim();\n const out: string[] = [];\n let index = 0;\n while (index < words.length) {\n out.push(words[index]);\n index += 1;\n // Greedily consume every adjacent repeat of the phrase that just\n // ended at the output tail (largest phrase first, then re-check so\n // \"I I I think\" fully collapses to \"I think\").\n let matched = true;\n while (matched) {\n matched = false;\n for (let size = 4; size >= 1; size--) {\n if (out.length < size || index + size > words.length) continue;\n const tail = out.slice(-size).join(\" \").toLowerCase();\n // Spoken digit sequences legitimately repeat (\"555 555 1234\");\n // never collapse a phrase that carries no letters.\n if (!/\\p{L}/u.test(tail)) continue;\n const ahead = words.slice(index, index + size).join(\" \").toLowerCase();\n if (tail === ahead) {\n index += size;\n matched = true;\n break;\n }\n }\n }\n }\n return out.join(\" \");\n}\n\n/**\n * Heuristic ASR-garbage detector. Intentionally conservative: it only\n * drops segments that carry no plausible information.\n */\nexport function isLowQualitySegment(text: string): boolean {\n const trimmed = text.trim();\n if (trimmed.length === 0) return true;\n // Single repeated character runs (\"aaaaaa\", \"######\").\n if (/^(.)\\1{4,}$/.test(trimmed)) return true;\n // Mostly non-letter content with no digits (timestamps/amounts are\n // information; \"%$#@!\" is not).\n const letters = trimmed.replace(/[^\\p{L}\\p{N}]/gu, \"\");\n if (letters.length === 0) return true;\n if (trimmed.length >= 12 && letters.length / trimmed.length < 0.3) {\n return true;\n }\n // One identical token repeated many times (\"yeah yeah yeah yeah yeah\").\n const words = trimmed.toLowerCase().split(/\\s+/);\n if (words.length >= 5) {\n const unique = new Set(words);\n if (unique.size === 1) return true;\n }\n return false;\n}\n\nexport function normalizeWhitespace(text: string): string {\n return text.replace(/\\s+/g, \" \").trim();\n}\n","/**\n * Wearable sync-state ledger — per-source bookkeeping for incremental\n * syncs, stored at `state/wearables/sync.json`.\n *\n * State is written only AFTER a source's transcripts (and any memories)\n * were persisted successfully, so a failed sync never advances the\n * watermark past data that didn't land.\n */\n\nimport { promises as fsPromises } from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface WearableSourceSyncState {\n /** ISO timestamp of the last successful sync run. */\n lastSyncAt: string;\n /** Most recent day (YYYY-MM-DD) that was synced. */\n lastDateSynced: string;\n /** Body hashes of the last-written day files, keyed by date. */\n dayHashes: Record<string, string>;\n /**\n * Body hashes of days whose memory-extraction pass completed without\n * warnings, keyed by date. A day whose entry is missing or stale\n * re-extracts on the next sync even when its transcript is unchanged\n * — this is how a memory pass that failed mid-run (transcript stored,\n * memories incomplete) self-heals. Absent on records written before\n * this field existed.\n */\n memoryDayHashes?: Record<string, string>;\n /** Native-memory ids already imported (bounded, newest kept). */\n importedNativeMemoryIds: string[];\n}\n\nexport interface WearableSyncStateFile {\n version: 1;\n sources: Record<string, WearableSourceSyncState>;\n}\n\n/** Cap on remembered native-memory ids per source. */\nconst MAX_TRACKED_NATIVE_IDS = 5_000;\n/** Cap on remembered day hashes per source (~2 years of days). */\nconst MAX_TRACKED_DAY_HASHES = 800;\n\nexport function syncStateFilePath(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"wearables\", \"sync.json\");\n}\n\nexport function emptySyncState(): WearableSyncStateFile {\n return { version: 1, sources: {} };\n}\n\nexport async function loadSyncState(\n memoryDir: string,\n): Promise<WearableSyncStateFile> {\n const filePath = syncStateFilePath(memoryDir);\n let raw: string;\n try {\n raw = await fsPromises.readFile(filePath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return emptySyncState();\n }\n throw err;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupt state file should not brick syncing forever; treat it\n // as a cold start (the worst case is re-syncing days we already\n // have, which the day-hash skip makes cheap and idempotent).\n return emptySyncState();\n }\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n Array.isArray(parsed) ||\n typeof (parsed as WearableSyncStateFile).sources !== \"object\" ||\n (parsed as WearableSyncStateFile).sources === null\n ) {\n return emptySyncState();\n }\n return { version: 1, sources: (parsed as WearableSyncStateFile).sources };\n}\n\nexport async function saveSyncState(\n memoryDir: string,\n state: WearableSyncStateFile,\n): Promise<void> {\n const filePath = syncStateFilePath(memoryDir);\n await fsPromises.mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now().toString(36)}`;\n await fsPromises.writeFile(\n tmpPath,\n `${JSON.stringify(state, null, 2)}\\n`,\n \"utf-8\",\n );\n try {\n await fsPromises.rename(tmpPath, filePath);\n } catch (err) {\n await fsPromises.unlink(tmpPath).catch(() => undefined);\n throw err;\n }\n}\n\n/** Merge a completed source sync into the state file shape. */\nexport function updateSourceSyncState(\n state: WearableSyncStateFile,\n sourceId: string,\n update: {\n syncedAt: string;\n days: string[];\n dayHashes: Record<string, string>;\n /** Days whose memory pass completed cleanly this run. */\n memoryDayHashes?: Record<string, string>;\n /**\n * Days whose memory pass ran this sync but did NOT complete\n * cleanly. Their previous completion records are removed so a\n * stale hash from an earlier clean pass can never mask the\n * failure on the next sync.\n */\n clearMemoryDays?: string[];\n importedNativeMemoryIds?: string[];\n },\n): WearableSyncStateFile {\n const previous = state.sources[sourceId];\n const mergedHashes: Record<string, string> = {\n ...(previous?.dayHashes ?? {}),\n ...update.dayHashes,\n };\n // Bound the hash map: keep the lexicographically-largest (most\n // recent) dates, which sort naturally for YYYY-MM-DD keys.\n const hashKeys = Object.keys(mergedHashes).sort();\n while (hashKeys.length > MAX_TRACKED_DAY_HASHES) {\n const oldest = hashKeys.shift();\n if (oldest === undefined) break;\n delete mergedHashes[oldest];\n }\n\n const mergedMemoryHashes: Record<string, string> = {\n ...(previous?.memoryDayHashes ?? {}),\n ...(update.memoryDayHashes ?? {}),\n };\n for (const day of update.clearMemoryDays ?? []) {\n if (!(day in (update.memoryDayHashes ?? {}))) {\n delete mergedMemoryHashes[day];\n }\n }\n const memoryHashKeys = Object.keys(mergedMemoryHashes).sort();\n while (memoryHashKeys.length > MAX_TRACKED_DAY_HASHES) {\n const oldest = memoryHashKeys.shift();\n if (oldest === undefined) break;\n delete mergedMemoryHashes[oldest];\n }\n\n const mergedNativeIds = [\n ...(previous?.importedNativeMemoryIds ?? []),\n ...(update.importedNativeMemoryIds ?? []),\n ];\n const dedupedNativeIds = [...new Set(mergedNativeIds)];\n const boundedNativeIds =\n dedupedNativeIds.length > MAX_TRACKED_NATIVE_IDS\n ? dedupedNativeIds.slice(dedupedNativeIds.length - MAX_TRACKED_NATIVE_IDS)\n : dedupedNativeIds;\n\n const sortedDays = [...update.days].sort();\n const latestDay = sortedDays[sortedDays.length - 1];\n const lastDateSynced =\n latestDay !== undefined &&\n (!previous || previous.lastDateSynced < latestDay)\n ? latestDay\n : previous?.lastDateSynced ?? latestDay ?? \"\";\n\n return {\n version: 1,\n sources: {\n ...state.sources,\n [sourceId]: {\n lastSyncAt: update.syncedAt,\n lastDateSynced,\n dayHashes: mergedHashes,\n memoryDayHashes: mergedMemoryHashes,\n importedNativeMemoryIds: boundedNativeIds,\n },\n },\n };\n}\n","/**\n * Wearable memory trust scoring — the \"smart\" memoryMode engine.\n *\n * Grounded in how production memory systems handle noisy ingest:\n *\n * - **LLM-as-judge gating** (Remnic's own extraction judge,\n * `judgeFactDurability`): an LLM verdict on durability decides\n * accept / defer / reject before anything persists.\n * - **Provenance priors**: each source carries a configurable trust\n * prior (`sourceTrust`) — a noisy ASR channel contributes less\n * confidence than a clean one (provenance-weighted fusion).\n * - **Corroboration**: independent agreement raises trust. A fact\n * supported by a second wearable that recorded the same day, or by\n * an existing active memory, is far less likely to be an ASR\n * artifact (ensemble agreement / self-consistency).\n *\n * The combined score maps to a three-way decision:\n *\n * trust >= autoApproveTrust -> written ACTIVE\n * trust >= reviewTrust -> written pending_review\n * below -> dropped\n *\n * with the judge able to short-circuit (reject -> drop,\n * defer -> pending_review regardless of score). The score, verdict,\n * and corroboration evidence persist on the memory (confidence +\n * structuredAttributes + verificationState), so Remnic's existing\n * trust machinery — memory-worth outcome counters, pattern\n * reinforcement, temporal supersession, contradiction scans — keeps\n * calibrating these memories after they land.\n */\n\nimport {\n countRecallTokenOverlap,\n normalizeRecallTokens,\n} from \"../recall-tokenization.js\";\n\n/** Judge adjustments and corroboration boosts (documented, fixed). */\nexport const TRUST_JUDGE_ACCEPT_BOOST = 0.15;\nexport const TRUST_CROSS_SOURCE_BOOST = 0.15;\nexport const TRUST_SUPPORTING_MEMORY_BOOST = 0.1;\n\n/** Minimum distinct tokens before similarity is meaningful. */\nconst MIN_FACT_TOKENS = 4;\n/** Fact-token coverage required to call a day-text corroborating. */\nconst CROSS_SOURCE_COVERAGE = 0.6;\n/** Fact-token coverage required to call an existing memory supporting. */\nconst MEMORY_SUPPORT_COVERAGE = 0.7;\n/** Bound on existing memories scanned per fact batch. */\nconst MAX_MEMORIES_SCANNED = 5_000;\n\nexport interface TrustEvidence {\n /** Other-source ids whose same-day content corroborates the fact. */\n corroboratedBySources: string[];\n /** Id of an existing active memory whose content supports the fact. */\n supportingMemoryId?: string;\n}\n\nexport interface TrustScoreInput {\n /** Extraction confidence for the fact (defaults to 0.7 when absent). */\n extractionConfidence: number | undefined;\n /** Per-source trust prior from config (0..1). */\n sourceTrust: number;\n /** Judge verdict kind when a judge ran; undefined when unavailable. */\n judgeVerdict?: \"accept\" | \"reject\" | \"defer\";\n evidence: TrustEvidence;\n}\n\nexport function computeTrustScore(input: TrustScoreInput): number {\n const confidence =\n typeof input.extractionConfidence === \"number\" &&\n Number.isFinite(input.extractionConfidence)\n ? Math.min(1, Math.max(0, input.extractionConfidence))\n : 0.7;\n let trust = confidence * Math.min(1, Math.max(0, input.sourceTrust));\n if (input.judgeVerdict === \"accept\") trust += TRUST_JUDGE_ACCEPT_BOOST;\n if (input.evidence.corroboratedBySources.length > 0) {\n trust += TRUST_CROSS_SOURCE_BOOST;\n }\n if (input.evidence.supportingMemoryId !== undefined) {\n trust += TRUST_SUPPORTING_MEMORY_BOOST;\n }\n return Math.min(1, Math.max(0, trust));\n}\n\nexport interface CorroborationContext {\n /**\n * Same-day transcript bodies from OTHER sources, keyed by source id.\n * Pre-tokenized once per sync (day bodies are large).\n */\n otherSourceDayTokens: Map<string, Set<string>>;\n /** Existing active memories: id + content. */\n existingMemories: Array<{ id: string; content: string }>;\n}\n\n/** Tokenize a day body once for repeated per-fact coverage checks. */\nexport function tokenizeDayBody(body: string): Set<string> {\n return new Set(normalizeRecallTokens(body));\n}\n\n/**\n * Find corroborating evidence for one fact. Deterministic and local:\n * token-coverage similarity via the shared recall tokenizer — no LLM\n * cost on the corroboration path.\n */\nexport function findCorroboration(\n factText: string,\n context: CorroborationContext,\n): TrustEvidence {\n const factTokens = normalizeRecallTokens(factText);\n const evidence: TrustEvidence = { corroboratedBySources: [] };\n if (factTokens.length < MIN_FACT_TOKENS) return evidence;\n const factTokenSet = new Set(factTokens);\n\n for (const [sourceId, dayTokens] of context.otherSourceDayTokens) {\n let matches = 0;\n for (const token of factTokenSet) {\n if (dayTokens.has(token)) matches += 1;\n }\n if (matches / factTokenSet.size >= CROSS_SOURCE_COVERAGE) {\n evidence.corroboratedBySources.push(sourceId);\n }\n }\n evidence.corroboratedBySources.sort();\n\n let scanned = 0;\n for (const memory of context.existingMemories) {\n if (scanned >= MAX_MEMORIES_SCANNED) break;\n scanned += 1;\n const matches = countRecallTokenOverlap(factTokenSet, memory.content);\n if (matches / factTokenSet.size >= MEMORY_SUPPORT_COVERAGE) {\n evidence.supportingMemoryId = memory.id;\n break;\n }\n }\n return evidence;\n}\n\nexport interface SmartDecision {\n outcome: \"active\" | \"review\" | \"drop\";\n reason:\n | \"judge-rejected\"\n | \"judge-deferred\"\n | \"auto-approved\"\n | \"queued-for-review\"\n | \"below-trust\"\n | \"ambient-high-impact\";\n trust: number;\n}\n\nexport interface SmartDecisionOptions {\n /**\n * Deny auto-approval regardless of score (issue #2294). Set for high-impact\n * personal facts derived from ambient capture: clamping extraction\n * confidence to the speculative ceiling is not enough on its own, because\n * judge-accept plus cross-source and existing-memory boosts sum past\n * `autoApproveTrust` — and two devices in one room recording the same\n * television program corroborate each other perfectly. Such a candidate\n * tops out in the review queue.\n */\n capAtReview?: boolean;\n}\n\n/** Map judge verdict + trust score to the smart-mode decision. */\nexport function decideSmart(\n trust: number,\n judgeVerdict: \"accept\" | \"reject\" | \"defer\" | undefined,\n thresholds: { autoApproveTrust: number; reviewTrust: number },\n options?: SmartDecisionOptions,\n): SmartDecision {\n if (judgeVerdict === \"reject\") {\n return { outcome: \"drop\", reason: \"judge-rejected\", trust };\n }\n if (judgeVerdict === \"defer\") {\n return { outcome: \"review\", reason: \"judge-deferred\", trust };\n }\n if (trust >= thresholds.autoApproveTrust) {\n return options?.capAtReview === true\n ? { outcome: \"review\", reason: \"ambient-high-impact\", trust }\n : { outcome: \"active\", reason: \"auto-approved\", trust };\n }\n if (trust >= thresholds.reviewTrust) {\n return { outcome: \"review\", reason: \"queued-for-review\", trust };\n }\n return { outcome: \"drop\", reason: \"below-trust\", trust };\n}\n","/**\n * Wearable memory generation — trust-gated extraction from cleaned day\n * transcripts.\n *\n * Wearable ASR quality varies wildly between providers and rooms, so\n * unlike live-session extraction this path is gated per source:\n *\n * - memoryMode \"off\" -> never runs\n * - memoryMode \"review\" -> candidates land with status\n * \"pending_review\" (operator approves via\n * the existing review-queue surfaces)\n * - memoryMode \"auto\" -> candidates that pass every gate land\n * active\n *\n * Deterministic gates (applied in order, all modes except \"off\"):\n * 1. category gate — procedure / reasoning_trace candidates are\n * skipped (they need richer persistence than\n * this path provides)\n * 2. confidence floor — `minConfidence`\n * 3. importance floor — local `scoreImportance` >= `minImportance`\n * 4. dedup — storage content-hash index + intra-run set\n * 5. day cap — top `maxMemoriesPerDay` by importance score\n * (0 disables the cap)\n *\n * The extraction engine itself is injected so this module stays free of\n * LLM-client construction; callers hand in `orchestrator`-owned or\n * standalone engines alike.\n */\n\nimport { scoreImportance } from \"../importance.js\";\nimport { collectPersonEntityRefs, isHighImpactPersonalFact } from \"../ambient-provenance.js\";\nimport type { MemoryWriteResult } from \"../storage.js\";\nimport {\n composeMemoryEnvelope,\n type MemoryWriteInput,\n type SealedMemoryEnvelope,\n type WriteContext,\n} from \"../write-envelope.js\";\nimport { log } from \"../logger.js\";\nimport type { JudgeBatchResult, JudgeCandidate } from \"../extraction-judge.js\";\nimport { getVerdictKind } from \"../extraction-judge.js\";\nimport { describeErrorForOperator } from \"./errors.js\";\nimport {\n computeTrustScore,\n decideSmart,\n findCorroboration,\n type CorroborationContext,\n type TrustEvidence,\n} from \"./trust.js\";\nimport type {\n BufferTurn,\n ExtractedFact,\n ExtractionResult,\n ImportanceLevel,\n ImportanceScore,\n MemoryCategory,\n MemoryStatus,\n} from \"../types.js\";\nimport { resolveSpeaker, type SpeakerRegistry } from \"./speakers.js\";\nimport type {\n WearableConversation,\n WearableMemoryMode,\n WearableNativeMemory,\n WearableSourceSettings,\n} from \"./types.js\";\n\n/**\n * Memory status used for a given memory mode. Least-privilege default:\n * anything that is not explicitly \"auto\" lands in the review queue\n * rather than active recall.\n */\n/**\n * Compose a wearable-side envelope in salvage mode (issue #1989 PR4):\n * wearable transcripts and provider-native memories are MACHINE input —\n * one malformed field must not abort a whole day's sync. Drops are\n * warn-logged, never silent.\n */\nfunction composeSalvagedWearableEnvelope(\n input: MemoryWriteInput,\n ctx: WriteContext,\n): SealedMemoryEnvelope {\n const envelope = composeMemoryEnvelope(input, ctx, { salvage: true });\n if (envelope.salvageNotes.length > 0) {\n log.warn(`wearable write salvaged invalid fields: ${envelope.salvageNotes.join(\"; \")}`);\n }\n return envelope;\n}\n\nexport function memoryStatusForMode(\n mode: WearableMemoryMode,\n): Extract<MemoryStatus, \"active\" | \"pending_review\"> {\n return mode === \"auto\" || mode === \"smart\" ? \"active\" : \"pending_review\";\n}\n\n/** Narrow writer interface satisfied by `StorageManager`. */\nexport interface WearableMemoryWriter {\n /**\n * Sealed-envelope write entry point (issue #1989 PR4): cross-cutting\n * fields ride the composed envelope; per-write extras stay explicit.\n */\n writeSealedMemory(\n envelope: SealedMemoryEnvelope,\n extras: {\n importance?: ImportanceScore;\n contentHashSource?: string;\n status?: MemoryStatus;\n memoryKind?: \"episode\" | \"note\" | \"box\" | \"dream\" | \"procedural\";\n },\n ): Promise<MemoryWriteResult>;\n hasFactContentHash(content: string): Promise<boolean>;\n /**\n * Locate an earlier wearable write of the same content (any status).\n * Optional: enables in-place promotion when re-scoring with stronger\n * evidence.\n */\n findWearableMemoryByContent?(\n content: string,\n ): Promise<{ id: string; status: MemoryStatus | undefined } | null>;\n /**\n * Promote a pending_review wearable memory to active, merging trust\n * evidence. Returns false when missing or no longer pending.\n */\n promoteWearableMemory?(\n id: string,\n attributeUpdates: Record<string, string>,\n confidence?: number,\n ): Promise<boolean>;\n /**\n * Demote a pending_review wearable memory to rejected on an explicit\n * judge-reject re-verdict. Returns false when missing or no longer\n * pending. Active rows are never auto-demoted.\n */\n demoteWearableMemory?(\n id: string,\n attributeUpdates: Record<string, string>,\n ): Promise<boolean>;\n}\n\nexport interface WearableMemoryGenDeps {\n extract(turns: BufferTurn[]): Promise<ExtractionResult>;\n writer: WearableMemoryWriter;\n /**\n * LLM-as-judge batch evaluation (the existing extraction judge).\n * Absent when no judge is wired (degraded smart mode: trust scoring\n * runs on confidence x sourceTrust + corroboration alone).\n */\n judgeFacts?(candidates: JudgeCandidate[]): Promise<JudgeBatchResult>;\n /**\n * Corroboration evidence for smart mode: other sources' same-day\n * transcript tokens + existing active memories. Absent disables\n * corroboration boosts.\n */\n corroboration?: CorroborationContext;\n}\n\nexport interface WearableMemoryGenResult {\n created: number;\n /**\n * Writes the #1579 tombstone chokepoint downgraded to pending_review (no\n * active copy). Surfaced distinctly from `created` so sync summaries and\n * callers don't count blocked content as successfully created (#1645).\n */\n tombstoneBlocked: number;\n /** Earlier borderline writes promoted to active by new evidence. */\n promoted: number;\n /** Earlier pending writes retired by a fresh judge-reject verdict. */\n demoted: number;\n skipped: number;\n skippedByReason: Record<string, number>;\n /** Non-fatal problems (e.g. the extraction engine erroring). */\n warnings: string[];\n /**\n * True when every conversation was extracted (the pass may still\n * carry degraded-mode warnings, e.g. judge unavailable). False only\n * when extraction itself aborted mid-day — the signal callers use to\n * re-run the pass on the next sync. A degraded-but-complete pass\n * must NOT re-run forever: its facts are already written and dedup\n * would suppress improvements anyway.\n */\n completed: boolean;\n}\n\nexport const WEARABLE_SOURCE_PREFIX = \"wearable\";\n\nexport function wearableSourceLabel(sourceId: string): string {\n return `${WEARABLE_SOURCE_PREFIX}:${sourceId}`;\n}\n\nexport function wearableDayTag(date: string): string {\n return `wearable-day:${date}`;\n}\n\nconst IMPORTANCE_RANK: Record<ImportanceLevel, number> = {\n trivial: 0,\n low: 1,\n normal: 2,\n high: 3,\n critical: 4,\n};\n\n/** Max characters of transcript per extraction turn. */\nconst MAX_EXTRACTION_CHUNK_CHARS = 6_000;\n/** Skip extraction for conversations with less substance than this. */\nconst MIN_CONVERSATION_CHARS = 80;\n\n/**\n * Render a cleaned conversation into extraction-ready turns. Each turn\n * carries a labeled multi-speaker transcript block; the wearer is\n * marked \"(you)\" so first-person facts attribute correctly.\n */\nexport function buildExtractionTurns(\n sourceId: string,\n date: string,\n conversation: WearableConversation,\n registry: SpeakerRegistry,\n): BufferTurn[] {\n const headerParts = [\n `Wearable transcript (${sourceId}) — ${date}`,\n conversation.title ? `\"${conversation.title}\"` : undefined,\n conversation.location ? `at ${conversation.location}` : undefined,\n ].filter((part): part is string => typeof part === \"string\");\n const header = `[${headerParts.join(\" — \")}]`;\n\n const lines: string[] = [];\n for (const segment of conversation.segments) {\n const { label } = resolveSpeaker(sourceId, segment, registry);\n lines.push(`${label}: ${segment.text}`);\n }\n const transcript = lines.join(\"\\n\");\n if (transcript.trim().length < MIN_CONVERSATION_CHARS) return [];\n\n const sessionKey = `wearables:${sourceId}:${date}:${conversation.id}`;\n const timestamp = conversation.startIso;\n const turns: BufferTurn[] = [];\n let chunkLines: string[] = [];\n let chunkChars = 0;\n const flush = () => {\n if (chunkLines.length === 0) return;\n turns.push({\n role: \"user\",\n content: `${header}\\n${chunkLines.join(\"\\n\")}`,\n timestamp,\n sourceValidAt: timestamp,\n sessionKey,\n // Always-on capture: this transcript may carry TV, podcast, or\n // overheard speech the wearer never authored (issue #2294).\n ambientCapture: true,\n });\n chunkLines = [];\n chunkChars = 0;\n };\n for (const line of transcript.split(\"\\n\")) {\n if (chunkChars + line.length + 1 > MAX_EXTRACTION_CHUNK_CHARS) flush();\n chunkLines.push(line);\n chunkChars += line.length + 1;\n }\n flush();\n return turns;\n}\n\ninterface GatedCandidate {\n fact: ExtractedFact;\n importance: ImportanceScore;\n conversation: WearableConversation;\n /**\n * Ambient high-impact verdict, decided where the extraction's entity list is\n * still in scope so personhood comes from the extractor's own `type:\n * \"person\"` metadata rather than the spelling of `entityRef` (#2294).\n */\n highImpact: boolean;\n}\n\ninterface ScoredCandidate {\n trust: number;\n verdict?: \"accept\" | \"reject\" | \"defer\";\n evidence: TrustEvidence;\n}\n\n/**\n * Smart-mode scoring: one judge batch call for the whole day, then\n * per-fact trust = confidence x sourceTrust + judge/corroboration\n * boosts. A judge failure degrades gracefully (warned once; scoring\n * continues on confidence + corroboration alone).\n */\nasync function scoreCandidates(\n novel: GatedCandidate[],\n settings: WearableSourceSettings,\n deps: WearableMemoryGenDeps,\n result: WearableMemoryGenResult,\n): Promise<Map<number, ScoredCandidate>> {\n const scored = new Map<number, ScoredCandidate>();\n if (novel.length === 0) return scored;\n\n let verdicts: Map<number, \"accept\" | \"reject\" | \"defer\"> | undefined;\n if (deps.judgeFacts) {\n const judgeCandidates: JudgeCandidate[] = novel.map((candidate) => ({\n text: candidate.fact.content,\n category: candidate.fact.category,\n confidence:\n typeof candidate.fact.confidence === \"number\"\n ? candidate.fact.confidence\n : 0.7,\n tags: candidate.fact.tags ?? [],\n importanceLevel: candidate.importance.level,\n }));\n try {\n const judgeResult = await deps.judgeFacts(judgeCandidates);\n verdicts = new Map();\n for (const [index, verdict] of judgeResult.verdicts) {\n verdicts.set(index, getVerdictKind(verdict));\n }\n } catch (err) {\n result.warnings.push(\n `extraction judge unavailable for this pass: ${describeErrorForOperator(err)} — trust scoring continued without judge verdicts`,\n );\n }\n }\n\n const corroboration: CorroborationContext = deps.corroboration ?? {\n otherSourceDayTokens: new Map(),\n existingMemories: [],\n };\n novel.forEach((candidate, index) => {\n const evidence = findCorroboration(candidate.fact.content, corroboration);\n const verdict = verdicts?.get(index);\n const trust = computeTrustScore({\n extractionConfidence: candidate.fact.confidence,\n sourceTrust: settings.sourceTrust,\n judgeVerdict: verdict,\n evidence,\n });\n scored.set(index, { trust, ...(verdict !== undefined ? { verdict } : {}), evidence });\n });\n return scored;\n}\n\n/**\n * Run extraction + gates over a day's conversations and persist the\n * survivors. Returns counts for the sync summary.\n */\nexport async function generateWearableMemories(\n sourceId: string,\n date: string,\n conversations: WearableConversation[],\n settings: WearableSourceSettings,\n registry: SpeakerRegistry,\n deps: WearableMemoryGenDeps,\n): Promise<WearableMemoryGenResult> {\n const result: WearableMemoryGenResult = {\n created: 0,\n tombstoneBlocked: 0,\n promoted: 0,\n demoted: 0,\n skipped: 0,\n skippedByReason: {},\n warnings: [],\n completed: true,\n };\n if (settings.memoryMode === \"off\") return result;\n\n const skip = (reason: string, count = 1): void => {\n result.skipped += count;\n result.skippedByReason[reason] =\n (result.skippedByReason[reason] ?? 0) + count;\n };\n\n const candidates: GatedCandidate[] = [];\n const seenContent = new Set<string>();\n\n for (const conversation of conversations) {\n const turns = buildExtractionTurns(sourceId, date, conversation, registry);\n if (turns.length === 0) continue;\n let extraction: ExtractionResult;\n try {\n extraction = await deps.extract(turns);\n } catch (err) {\n // One failing extraction call almost always means every call will\n // fail (missing key, provider outage) — stop hammering the engine\n // and surface a single actionable warning instead of one per\n // conversation. Candidates gathered before the failure still\n // persist below.\n result.warnings.push(\n `extraction failed for ${sourceId}/${date} (conversation ${conversation.id}): ${describeErrorForOperator(err)} — the memory pass for this day retries on the next sync`,\n );\n result.completed = false;\n break;\n }\n const personRefs = collectPersonEntityRefs(extraction);\n for (const fact of extraction.facts) {\n const content = fact.content?.trim();\n if (!content) {\n skip(\"empty\");\n continue;\n }\n if (fact.category === \"procedure\" || fact.category === \"reasoning_trace\") {\n skip(\"unsupported-category\");\n continue;\n }\n const highImpact = isHighImpactPersonalFact({ ...fact, content }, personRefs);\n // In smart mode the trust bands subsume the hard confidence\n // floor — a borderline fact belongs in the review band, not on\n // the floor. The pre-filter applies to review/auto modes only.\n //\n // High-impact ambient claims are exempt: the extraction clamp just\n // pinned them to 0.39, which is below the 0.6 default floor, so the\n // pre-filter would silently drop the very candidates `review` mode\n // promises to queue and would make the auto-mode cap unreachable\n // (#2294). They skip the floor and land pending_review via the cap.\n if (\n highImpact === false &&\n settings.memoryMode !== \"smart\" &&\n typeof fact.confidence === \"number\" &&\n fact.confidence < settings.minConfidence\n ) {\n skip(\"below-confidence\");\n continue;\n }\n const importance = scoreImportance(content, fact.category, fact.tags ?? []);\n if (\n IMPORTANCE_RANK[importance.level] <\n IMPORTANCE_RANK[settings.minImportance]\n ) {\n skip(\"below-importance\");\n continue;\n }\n const dedupKey = content.toLowerCase();\n if (seenContent.has(dedupKey)) {\n skip(\"duplicate-in-run\");\n continue;\n }\n seenContent.add(dedupKey);\n candidates.push({\n fact: { ...fact, content },\n importance,\n conversation,\n highImpact,\n });\n }\n }\n\n // Drop candidates that already exist in storage BEFORE applying the\n // day cap so duplicates never consume cap slots that novel,\n // lower-scoring candidates should get (Codex P2 on PR #1458). In\n // smart mode a duplicate of a PENDING_REVIEW write is kept aside as\n // a promotion candidate — corroboration that arrives after the\n // original borderline write (another device syncing the same day)\n // must be able to promote it in place (Cursor review on PR #1462).\n const novel: GatedCandidate[] = [];\n const promotable: GatedCandidate[] = [];\n for (const candidate of candidates) {\n if (await deps.writer.hasFactContentHash(candidate.fact.content)) {\n if (\n settings.memoryMode === \"smart\" &&\n deps.writer.findWearableMemoryByContent !== undefined &&\n deps.writer.promoteWearableMemory !== undefined\n ) {\n promotable.push(candidate);\n } else {\n skip(\"duplicate-existing\");\n }\n continue;\n }\n novel.push(candidate);\n }\n\n // Re-score promotion candidates with TODAY'S evidence; promote the\n // ones that now clear the auto threshold. Never consumes day-cap\n // slots (no new memory is written).\n if (promotable.length > 0) {\n const promoteScores = await scoreCandidates(promotable, settings, deps, result);\n for (const [index, candidate] of promotable.entries()) {\n const scored = promoteScores.get(index);\n // Every wearable candidate is ambient by construction, so a high-impact\n // personal claim can only ever reach the review queue — late-arriving\n // corroboration must not promote a clamped TV line to active (#2294).\n const decision = scored\n ? decideSmart(scored.trust, scored.verdict, settings, {\n capAtReview: candidate.highImpact,\n })\n : undefined;\n if (!scored || !decision) {\n skip(\"duplicate-existing\");\n continue;\n }\n // A fresh judge-REJECT retires the stored row — but only a\n // pending_review one. Active rows are never auto-demoted: an\n // operator approval or accrued recall signals must not be\n // overturned by one later LLM verdict; contradiction scans and\n // supersession own active-row retirement (Cursor review on PR\n // #1462, round 7).\n if (scored.verdict === \"reject\") {\n if (deps.writer.demoteWearableMemory !== undefined) {\n const existingForDemote = await deps.writer.findWearableMemoryByContent!(\n candidate.fact.content,\n );\n if (\n existingForDemote &&\n existingForDemote.status === \"pending_review\" &&\n (await deps.writer.demoteWearableMemory(existingForDemote.id, {\n trustScore: scored.trust.toFixed(3),\n trustDecision: \"demoted-by-rejection\",\n judgeVerdict: \"reject\",\n }))\n ) {\n result.demoted += 1;\n continue;\n }\n }\n skip(\"duplicate-existing\");\n continue;\n }\n if (decision.outcome !== \"active\") {\n skip(\"duplicate-existing\");\n continue;\n }\n const existing = await deps.writer.findWearableMemoryByContent!(\n candidate.fact.content,\n );\n if (!existing || existing.status !== \"pending_review\") {\n skip(\"duplicate-existing\");\n continue;\n }\n const promoted = await deps.writer.promoteWearableMemory!(\n existing.id,\n {\n trustScore: scored.trust.toFixed(3),\n trustDecision: \"promoted-by-corroboration\",\n ...(scored.verdict !== undefined ? { judgeVerdict: scored.verdict } : {}),\n ...(scored.evidence.corroboratedBySources.length > 0\n ? { corroboratedBySources: scored.evidence.corroboratedBySources.join(\",\") }\n : {}),\n ...(scored.evidence.supportingMemoryId !== undefined\n ? { supportingMemoryId: scored.evidence.supportingMemoryId }\n : {}),\n },\n scored.trust,\n );\n if (promoted) {\n result.promoted += 1;\n } else {\n skip(\"duplicate-existing\");\n }\n }\n }\n\n // Smart mode: judge + trust scoring decide active/review/drop per\n // fact. The judge runs ONE batch call for the whole day; trust\n // combines extraction confidence x sourceTrust with corroboration\n // boosts (cross-device agreement, existing-memory support).\n let trustById = new Map<number, ScoredCandidate>();\n if (settings.memoryMode === \"smart\") {\n trustById = await scoreCandidates(novel, settings, deps, result);\n }\n\n // Smart decisions run BEFORE the day cap so dropped facts (judge\n // rejections, below-trust) never consume cap slots that surviving\n // candidates ranked past position N should get (Cursor review on PR\n // #1462).\n interface Writable {\n candidate: GatedCandidate;\n index: number;\n status: MemoryStatus;\n trustAttributes: Record<string, string>;\n }\n const modeStatus = memoryStatusForMode(settings.memoryMode);\n const writable: Writable[] = [];\n novel.forEach((candidate, index) => {\n // Every wearable candidate is ambient, so a high-impact personal claim can\n // never be written active — on ANY mode. `auto` has no trust scoring and an\n // operator may set minConfidence at or below the speculative ceiling, which\n // would otherwise let a clamped TV line straight into recall (#2294).\n const capAtReview = candidate.highImpact;\n if (settings.memoryMode !== \"smart\") {\n const status = capAtReview ? \"pending_review\" : modeStatus;\n writable.push({\n candidate,\n index,\n status,\n trustAttributes: capAtReview ? { trustDecision: \"ambient-high-impact\" } : {},\n });\n return;\n }\n const scored = trustById.get(index);\n if (!scored) return;\n const decision = decideSmart(scored.trust, scored.verdict, settings, { capAtReview });\n if (decision.outcome === \"drop\") {\n skip(decision.reason);\n return;\n }\n writable.push({\n candidate,\n index,\n status: decision.outcome === \"active\" ? \"active\" : \"pending_review\",\n trustAttributes: {\n trustScore: scored.trust.toFixed(3),\n trustDecision: decision.reason,\n ...(scored.verdict !== undefined ? { judgeVerdict: scored.verdict } : {}),\n ...(scored.evidence.corroboratedBySources.length > 0\n ? { corroboratedBySources: scored.evidence.corroboratedBySources.join(\",\") }\n : {}),\n ...(scored.evidence.supportingMemoryId !== undefined\n ? { supportingMemoryId: scored.evidence.supportingMemoryId }\n : {}),\n },\n });\n });\n\n // Day cap over the SURVIVORS: strongest by trust in smart mode, by\n // importance otherwise. Stable ordering with a content tiebreak.\n const strength = (entry: Writable): number =>\n settings.memoryMode === \"smart\"\n ? trustById.get(entry.index)?.trust ?? 0\n : entry.candidate.importance.score;\n writable.sort((a, b) => {\n const sa = strength(a);\n const sb = strength(b);\n if (sa > sb) return -1;\n if (sa < sb) return 1;\n if (a.candidate.fact.content < b.candidate.fact.content) return -1;\n if (a.candidate.fact.content > b.candidate.fact.content) return 1;\n return 0;\n });\n const cap = settings.maxMemoriesPerDay;\n const kept = cap > 0 ? writable.slice(0, cap) : writable;\n if (writable.length > kept.length) {\n skip(\"over-day-cap\", writable.length - kept.length);\n }\n\n for (const { candidate, index, status, trustAttributes } of kept) {\n const tags = [\n ...new Set([\n ...(candidate.fact.tags ?? []),\n WEARABLE_SOURCE_PREFIX,\n wearableSourceLabel(sourceId),\n wearableDayTag(date),\n ]),\n ];\n const factEnvelope = composeSalvagedWearableEnvelope(\n {\n content: candidate.fact.content,\n category: candidate.fact.category,\n confidence:\n settings.memoryMode === \"smart\"\n ? trustById.get(index)?.trust\n : candidate.fact.confidence,\n tags,\n validAt: candidate.conversation.startIso,\n structuredAttributes: {\n ...(candidate.fact.structuredAttributes ?? {}),\n wearableSource: sourceId,\n wearableDate: date,\n wearableConversationId: candidate.conversation.id,\n ...trustAttributes,\n },\n },\n { source: wearableSourceLabel(sourceId) },\n );\n const writeResult = await deps.writer.writeSealedMemory(factEnvelope, {\n importance: candidate.importance,\n contentHashSource: candidate.fact.content,\n status,\n });\n // #1645: a tombstone-blocked wearable write lands pending_review (no active\n // copy) — count it as blocked, NOT created, so the sync summary and callers\n // don't report blocked content as successfully created active memories.\n if (writeResult.tombstoneBlocked) {\n result.tombstoneBlocked += 1;\n } else {\n result.created += 1;\n }\n }\n return result;\n}\n\n/**\n * Write one compact daily-digest memory summarizing the day's recorded\n * conversations. Deterministic (no LLM): titles, time ranges, speaker\n * counts. Gated by `wearables.digestEnabled`.\n */\nexport async function writeDailyDigestMemory(\n sourceId: string,\n date: string,\n conversations: WearableConversation[],\n settings: WearableSourceSettings,\n registry: SpeakerRegistry,\n writer: WearableMemoryWriter,\n): Promise<boolean> {\n if (settings.memoryMode === \"off\") return false;\n if (conversations.length === 0) return false;\n const lines = conversations.map((conversation) => {\n const title = conversation.title?.trim() || \"Untitled conversation\";\n const speakers = new Set(\n conversation.segments.map(\n (segment) => resolveSpeaker(sourceId, segment, registry).label,\n ),\n );\n return `- ${title} (${speakers.size} speaker${speakers.size === 1 ? \"\" : \"s\"})`;\n });\n const content =\n `Wearable day digest — ${sourceId}, ${date}: ` +\n `${conversations.length} recorded conversation${conversations.length === 1 ? \"\" : \"s\"}.\\n` +\n lines.join(\"\\n\");\n if (await writer.hasFactContentHash(content)) return false;\n const digestEnvelope = composeSalvagedWearableEnvelope(\n {\n content,\n category: \"moment\",\n confidence: 0.9,\n tags: [\n WEARABLE_SOURCE_PREFIX,\n wearableSourceLabel(sourceId),\n wearableDayTag(date),\n \"daily-digest\",\n ],\n validAt: `${date}T00:00:00.000Z`,\n structuredAttributes: {\n wearableSource: sourceId,\n wearableDate: date,\n },\n },\n { source: wearableSourceLabel(sourceId) },\n );\n // A provider derives conversation.title from the same captured audio, so a\n // title lifted from a television scene (\"Dana's cancer diagnosis\") would\n // otherwise ride into recall on the digest, bypassing the extraction prompt\n // and its clamp entirely. Digest content is `moment`, which the classifier\n // already treats as personal-claim-shaped, so this reads the titles alone\n // and holds only a contaminated digest for review (#2294).\n const titleIsHighImpact = lines.some((line) =>\n isHighImpactPersonalFact({ category: \"fact\", content: line }),\n );\n await writer.writeSealedMemory(digestEnvelope, {\n importance: scoreImportance(content, \"moment\", [\"daily-digest\"]),\n contentHashSource: content,\n status: titleIsHighImpact ? \"pending_review\" : memoryStatusForMode(settings.memoryMode),\n memoryKind: \"episode\",\n });\n return true;\n}\n\n/**\n * Import provider-extracted memories (Bee facts, Omi memories) into the\n * review queue. Always `pending_review` regardless of memoryMode — the\n * provider's extraction quality is outside Remnic's control.\n */\n/** Native-source trust prior reduction (provider extraction quality). */\nconst NATIVE_TRUST_FACTOR = 0.9;\n\nexport async function importNativeMemories(\n sourceId: string,\n memories: WearableNativeMemory[],\n alreadyImportedIds: ReadonlySet<string>,\n settings: WearableSourceSettings,\n deps: WearableMemoryGenDeps,\n): Promise<{ imported: number; tombstoneBlocked: number; importedIds: string[]; warnings: string[] }> {\n let imported = 0;\n let tombstoneBlocked = 0;\n const importedIds: string[] = [];\n const warnings: string[] = [];\n const seenContent = new Set<string>();\n const smart = settings.importNativeMemories === \"smart\";\n\n // Smart path: one judge batch over the novel items, like transcript\n // facts, with a reduced source prior — provider extraction quality is\n // outside Remnic's control.\n const novel: WearableNativeMemory[] = [];\n for (const memory of memories) {\n const content = memory.content?.trim();\n if (!content) continue;\n if (alreadyImportedIds.has(memory.id)) continue;\n // Intra-run + cross-run dedup: the storage hash index only learns a\n // fact after its write lands, so same-content items within one page\n // batch need the local set.\n if (seenContent.has(content) || (await deps.writer.hasFactContentHash(content))) {\n importedIds.push(memory.id);\n continue;\n }\n seenContent.add(content);\n novel.push({ ...memory, content });\n }\n\n let verdicts: Map<number, \"accept\" | \"reject\" | \"defer\"> | undefined;\n if (smart && deps.judgeFacts && novel.length > 0) {\n try {\n const judgeResult = await deps.judgeFacts(\n novel.map((memory) => ({\n text: memory.content,\n category: \"fact\",\n confidence: 0.7,\n tags: memory.tags ?? [],\n })),\n );\n verdicts = new Map();\n for (const [index, verdict] of judgeResult.verdicts) {\n verdicts.set(index, getVerdictKind(verdict));\n }\n } catch (err) {\n warnings.push(\n `extraction judge unavailable for native import: ${describeErrorForOperator(err)} — trust scoring continued without judge verdicts`,\n );\n }\n }\n const corroboration: CorroborationContext = deps.corroboration ?? {\n otherSourceDayTokens: new Map(),\n existingMemories: [],\n };\n\n for (const [index, memory] of novel.entries()) {\n const content = memory.content;\n let status: MemoryStatus = \"pending_review\";\n let trustAttributes: Record<string, string> = {};\n let confidence = 0.6;\n if (smart) {\n const evidence = findCorroboration(content, corroboration);\n const verdict = verdicts?.get(index);\n const trust = computeTrustScore({\n extractionConfidence: undefined,\n sourceTrust: settings.sourceTrust * NATIVE_TRUST_FACTOR,\n judgeVerdict: verdict,\n evidence,\n });\n const decision = decideSmart(trust, verdict, settings);\n if (decision.outcome === \"drop\") {\n // Deliberately NOT recorded in importedIds: a dropped native\n // fact re-fetches and re-scores on later syncs, so corpus or\n // corroboration support that arrives later can still admit it.\n // The judge verdict cache keeps repeated rejections cheap\n // (Cursor review on PR #1462).\n continue;\n }\n status = decision.outcome === \"active\" ? \"active\" : \"pending_review\";\n confidence = trust;\n trustAttributes = {\n trustScore: trust.toFixed(3),\n trustDecision: decision.reason,\n ...(verdict !== undefined ? { judgeVerdict: verdict } : {}),\n ...(evidence.corroboratedBySources.length > 0\n ? { corroboratedBySources: evidence.corroboratedBySources.join(\",\") }\n : {}),\n ...(evidence.supportingMemoryId !== undefined\n ? { supportingMemoryId: evidence.supportingMemoryId }\n : {}),\n };\n }\n const nativeEnvelope = composeSalvagedWearableEnvelope(\n {\n content,\n category: \"fact\",\n confidence,\n tags: [\n ...new Set([\n ...(memory.tags ?? []),\n WEARABLE_SOURCE_PREFIX,\n wearableSourceLabel(sourceId),\n \"native-import\",\n ]),\n ],\n validAt: memory.createdIso,\n structuredAttributes: {\n wearableSource: sourceId,\n wearableNativeId: memory.id,\n ...trustAttributes,\n },\n },\n { source: `${wearableSourceLabel(sourceId)}:native` },\n );\n const nativeWriteResult = await deps.writer.writeSealedMemory(nativeEnvelope, {\n importance: scoreImportance(content, \"fact\", memory.tags ?? []),\n contentHashSource: content,\n status,\n });\n // #1645: a tombstone-blocked native import lands pending_review (no active\n // copy) — count it as blocked, NOT imported. Still record the provider id in\n // importedIds so the next sync doesn't re-import and duplicate the pending\n // row; the block is permanent for this content.\n if (nativeWriteResult.tombstoneBlocked) {\n tombstoneBlocked += 1;\n } else {\n imported += 1;\n }\n importedIds.push(memory.id);\n }\n return { imported, tombstoneBlocked, importedIds, warnings };\n}\n","/**\n * Wearable sync pipeline — pull → clean → label → correct → store →\n * (optionally) remember, for one source.\n *\n * Stage order per conversation is deliberate:\n * 1. off-the-record elision (before anything can persist the span)\n * 2. cleanup (merging first lets redaction see numbers\n * that ASR split across segments)\n * 3. redaction (built-in + user patterns)\n * 4. corrections (user-specific word fixes)\n *\n * Day files are rebuilt idempotently; the per-day body hash recorded in\n * sync state lets unchanged days skip both the rewrite and the\n * (expensive) memory extraction. Sync state advances only after every\n * write for the run has succeeded.\n */\n\nimport { cleanConversation } from \"./cleanup.js\";\nimport { describeErrorForOperator, WearablesInputError } from \"./errors.js\";\nimport {\n applyCorrections,\n compileCorrectionRules,\n loadCorrectionsFile,\n type CompiledCorrectionRule,\n} from \"./corrections.js\";\nimport {\n composeDayTranscriptBody,\n composeDayTranscriptMeta,\n hashTranscriptBody,\n isValidTranscriptDate,\n serializeDayTranscript,\n} from \"./day-store.js\";\nimport {\n generateWearableMemories,\n importNativeMemories,\n writeDailyDigestMemory,\n type WearableMemoryGenDeps,\n} from \"./memory-gen.js\";\nimport { tokenizeDayBody, type CorroborationContext } from \"./trust.js\";\nimport { applyOffTheRecord, compileRedactionPatterns, redactText } from \"./redaction.js\";\nimport { loadSpeakerRegistry } from \"./speakers.js\";\nimport {\n loadSyncState,\n saveSyncState,\n updateSourceSyncState,\n} from \"./sync-state.js\";\nimport type {\n WearableConversation,\n WearableSourceConnector,\n WearableSourceSettings,\n WearableSyncSummary,\n WearablesConfig,\n} from \"./types.js\";\n\n/**\n * Pathological-provider backstop on pagination loops. Day fetches are\n * intentionally UNLIMITED for real data — a full day must never be\n * truncated — so runaway protection comes from cursor-cycle detection\n * plus this far-out-of-band ceiling, not from a content-sized cap.\n */\nconst PAGE_SAFETY_CEILING = 10_000;\n/** Default lookback window (today + yesterday) for unscoped syncs. */\nconst DEFAULT_SYNC_DAYS = 2;\nconst MAX_SYNC_DAYS = 90;\n\nexport interface WearableSyncOptions {\n /** Sync exactly this day (YYYY-MM-DD). Overrides `days`. */\n date?: string;\n /** Lookback window in days ending today (default 2, max 90). */\n days?: number;\n /** Re-run memory extraction even for unchanged days. */\n forceMemories?: boolean;\n signal?: AbortSignal;\n}\n\nexport interface WearableSyncDeps {\n /** Memory dir for state files (speakers, corrections, sync ledger). */\n memoryDir: string;\n /** Read the stored content hash for a day file, if present. */\n readDayContentHash(sourceId: string, date: string): Promise<string | null>;\n /** Persist a serialized day-transcript file (atomic). */\n writeDayTranscript(\n sourceId: string,\n date: string,\n serialized: string,\n ): Promise<void>;\n /**\n * Optional hook fired once after the sync wrote ANYTHING the search\n * index should see: day transcripts, created memories, in-place\n * promotions, or native imports. (A cross-source invalidation can\n * promote memories on a run with zero transcript writes — the index\n * must still refresh.)\n */\n afterWrites?(): Promise<void>;\n /**\n * Memory-generation dependencies, or null when no extraction engine\n * is available in this context (transcripts still sync; memory\n * creation is skipped with a warning when the mode wanted it).\n */\n memoryGen: WearableMemoryGenDeps | null;\n /**\n * Same-day transcript bodies from OTHER sources, for cross-device\n * corroboration in smart mode. Absent disables the boost.\n */\n readOtherSourceDayBodies?(\n date: string,\n excludeSource: string,\n ): Promise<Map<string, string>>;\n /**\n * Existing memories usable as support evidence: status \"active\" or\n * \"pending_review\" (explicit allow-list — a borderline fact observed\n * again on a later day is repetition signal, and the +0.10 boost is\n * how it earns promotion). Rejected/quarantined/superseded/archived/\n * forgotten rows are never support evidence.\n */\n listSupportMemories?(): Promise<Array<{ id: string; content: string }>>;\n /** Clock injection for tests. */\n now?: () => Date;\n}\n\n/** Format a Date as YYYY-MM-DD in the given IANA timezone. */\nexport function dateInTimezone(date: Date, timezone: string): string {\n try {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(date);\n const get = (type: string) =>\n parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n } catch {\n return date.toISOString().slice(0, 10);\n }\n}\n\n/** Resolve the list of days to sync, oldest first. */\nexport function resolveSyncDates(\n options: WearableSyncOptions,\n timezone: string,\n now: Date,\n): string[] {\n if (options.date !== undefined) {\n if (!isValidTranscriptDate(options.date)) {\n throw new WearablesInputError(\n `wearables sync: invalid date '${options.date}' — expected YYYY-MM-DD`,\n );\n }\n return [options.date];\n }\n let days = DEFAULT_SYNC_DAYS;\n if (options.days !== undefined) {\n if (\n !Number.isFinite(options.days) ||\n !Number.isInteger(options.days) ||\n options.days < 1 ||\n options.days > MAX_SYNC_DAYS\n ) {\n throw new WearablesInputError(\n `wearables sync: invalid days '${options.days}' — expected an integer between 1 and ${MAX_SYNC_DAYS}`,\n );\n }\n days = options.days;\n }\n // Walk back by CALENDAR days from today's local date — subtracting\n // fixed 24h intervals from the wall clock can skip a local day\n // around DST transitions (Codex P2 on PR #1458).\n const dates: string[] = [];\n let cursor = dateInTimezone(now, timezone);\n for (let count = 0; count < days; count++) {\n dates.unshift(cursor);\n cursor = previousIsoDate(cursor);\n }\n return dates;\n}\n\n/** Previous calendar date in pure date arithmetic (no DST exposure). */\nfunction previousIsoDate(date: string): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() - 1);\n return parsed.toISOString().slice(0, 10);\n}\n\nasync function fetchAllConversationsForDate(\n connector: WearableSourceConnector,\n date: string,\n timezone: string,\n signal: AbortSignal | undefined,\n warnings: string[],\n): Promise<{ conversations: WearableConversation[]; partial: boolean }> {\n // Keyed by conversation id: a looping or overlapping provider can\n // re-serve rows it already returned, and appending blindly would\n // store the same conversation twice in the day file (Cursor review\n // on PR #1464). Map keeps first-seen order; a re-served id replaces\n // its entry in place, so the provider's LATEST version of a\n // conversation wins — exactly what the current-day refresh wants.\n const byId = new Map<string, WearableConversation>();\n let cursor: string | null | undefined = undefined;\n const seenCursors = new Set<string>();\n const collect = () => [...byId.values()];\n for (let page = 0; page < PAGE_SAFETY_CEILING; page++) {\n const result = await connector.fetchConversations({\n date,\n timezone,\n cursor,\n signal,\n });\n for (const conversation of result.conversations) {\n byId.set(conversation.id, conversation);\n }\n if (!result.nextCursor) return { conversations: collect(), partial: false };\n // A repeated cursor means the provider's pagination is looping —\n // following it again would refetch the same page forever.\n if (seenCursors.has(result.nextCursor)) {\n warnings.push(\n `${connector.id}: provider pagination repeated cursor on ${date} — stopped to avoid an infinite loop; day may be partially synced (every sync refetches and re-warns while the provider misbehaves)`,\n );\n return { conversations: collect(), partial: true };\n }\n seenCursors.add(result.nextCursor);\n cursor = result.nextCursor;\n }\n warnings.push(\n `${connector.id}: stopped paginating ${date} after the ${PAGE_SAFETY_CEILING}-page safety ceiling — day may be partially synced (every sync refetches and re-warns while this persists)`,\n );\n return { conversations: collect(), partial: true };\n}\n\n/** Visible marker appended to day files whose fetch hit the page cap. */\nconst PARTIAL_DAY_MARKER =\n \"\\n*Note: pagination safety cap reached during sync — this day may be incomplete.*\\n\";\n\n/**\n * Explicit replacement body for a day whose provider data exists but\n * whose every segment was elided or dropped (all off-the-record, all\n * ASR garbage). Written so previously-stored content for the day stops\n * being searchable instead of lingering as a stale transcript.\n */\nfunction emptyDayBody(sourceId: string, date: string): string {\n return (\n `# ${sourceId} transcript — ${date}\\n\\n` +\n \"_No storable conversation content for this day (all segments were elided or dropped)._\\n\"\n );\n}\n\ninterface CleanedDay {\n conversations: WearableConversation[];\n segmentsKept: number;\n segmentsDropped: number;\n redactions: number;\n correctionsApplied: number;\n}\n\nfunction cleanDay(\n raw: WearableConversation[],\n sourceId: string,\n settings: WearableSourceSettings,\n config: WearablesConfig,\n userRedaction: RegExp[],\n correctionRules: CompiledCorrectionRule[],\n): CleanedDay {\n const { offTheRecordEnabled, redactionEnabled } = config;\n const out: CleanedDay = {\n conversations: [],\n segmentsKept: 0,\n segmentsDropped: 0,\n redactions: 0,\n correctionsApplied: 0,\n };\n for (const conversation of raw) {\n let current = conversation;\n if (offTheRecordEnabled) {\n const otr = applyOffTheRecord(current);\n current = otr.conversation;\n out.segmentsDropped += otr.droppedSegments;\n }\n const cleaned = cleanConversation(current, settings.cleanup);\n current = cleaned.conversation;\n out.segmentsDropped += cleaned.droppedSegments;\n\n const segments = current.segments.map((segment) => {\n let text = segment.text;\n if (redactionEnabled) {\n const redacted = redactText(text, userRedaction);\n text = redacted.text;\n out.redactions += redacted.redactions;\n }\n const corrected = applyCorrections(text, correctionRules, sourceId);\n out.correctionsApplied += corrected.applied;\n return { ...segment, text: corrected.text };\n });\n current = { ...current, segments };\n\n if (current.segments.length > 0) {\n out.conversations.push(current);\n out.segmentsKept += current.segments.length;\n }\n }\n return out;\n}\n\n/** Sync one source. */\nexport async function syncWearableSource(\n connector: WearableSourceConnector,\n settings: WearableSourceSettings,\n config: WearablesConfig,\n options: WearableSyncOptions,\n deps: WearableSyncDeps,\n): Promise<WearableSyncSummary> {\n const now = deps.now ? deps.now() : new Date();\n const timezone = config.timezone ?? defaultTimezone();\n const dates = resolveSyncDates(options, timezone, now);\n\n const summary: WearableSyncSummary = {\n source: connector.id,\n days: dates,\n conversations: 0,\n segmentsKept: 0,\n segmentsDropped: 0,\n redactions: 0,\n correctionsApplied: 0,\n transcriptsWritten: [],\n memoriesCreated: 0,\n memoriesPromoted: 0,\n memoriesDemoted: 0,\n memoriesSkipped: 0,\n memoriesBlocked: 0,\n nativeMemoriesImported: 0,\n warnings: [],\n };\n\n const registry = await loadSpeakerRegistry(deps.memoryDir);\n const stateRules = await loadCorrectionsFile(deps.memoryDir);\n const correctionRules = [\n ...compileCorrectionRules(config.corrections, \"wearables.corrections\"),\n ...compileCorrectionRules(stateRules, \"state corrections\"),\n ];\n const userRedaction = compileRedactionPatterns(config.redactionPatterns);\n\n let syncState = await loadSyncState(deps.memoryDir);\n const previousState = syncState.sources[connector.id];\n const dayHashes: Record<string, string> = {};\n const memoryDayHashes: Record<string, string> = {};\n const failedMemoryDays: string[] = [];\n const importedNativeIds: string[] = [];\n\n for (const date of dates) {\n const fetched = await fetchAllConversationsForDate(\n connector,\n date,\n timezone,\n options.signal,\n summary.warnings,\n );\n const cleaned = cleanDay(\n fetched.conversations,\n connector.id,\n settings,\n config,\n userRedaction,\n correctionRules,\n );\n summary.conversations += cleaned.conversations.length;\n summary.segmentsKept += cleaned.segmentsKept;\n summary.segmentsDropped += cleaned.segmentsDropped;\n summary.redactions += cleaned.redactions;\n summary.correctionsApplied += cleaned.correctionsApplied;\n\n if (fetched.conversations.length === 0) {\n // No provider data at all. A transient provider hiccup can\n // legitimately produce an empty result, so an existing stored\n // transcript is never auto-deleted here — surface it instead.\n const existing = await deps.readDayContentHash(connector.id, date);\n if (existing !== null) {\n summary.warnings.push(\n `${connector.id}: provider returned no conversations for ${date} but a stored transcript exists — leaving it in place; delete the day file manually if the recordings were intentionally removed upstream`,\n );\n }\n continue;\n }\n\n // Provider data exists but cleanup/off-the-record elided all of it:\n // replace any stored transcript with an explicit empty-day file so\n // elided content stops being stored and searchable (Codex P2 on PR\n // #1458).\n const allElided = cleaned.conversations.length === 0;\n let body = allElided\n ? emptyDayBody(connector.id, date)\n : composeDayTranscriptBody(\n connector.id,\n date,\n timezone,\n cleaned.conversations,\n registry,\n );\n if (fetched.partial && !allElided) {\n body += PARTIAL_DAY_MARKER;\n }\n const bodyHash = hashTranscriptBody(body);\n // The on-disk file is the authority for the skip decision — a hash\n // remembered in sync state must never suppress recreating a day\n // file that was deleted or lost (Cursor review on PR #1458). The\n // state's dayHashes remain as bookkeeping only.\n const existingHash = await deps.readDayContentHash(connector.id, date);\n const changed = existingHash !== bodyHash;\n // An all-elided day only writes a replacement over an existing\n // file; it never creates an empty-day file from nothing.\n const shouldWrite = changed && (!allElided || existingHash !== null);\n\n if (shouldWrite) {\n const meta = composeDayTranscriptMeta(\n connector.id,\n date,\n timezone,\n cleaned.conversations,\n registry,\n body,\n now.toISOString(),\n );\n await deps.writeDayTranscript(\n connector.id,\n date,\n serializeDayTranscript(meta, body),\n );\n summary.transcriptsWritten.push(date);\n }\n dayHashes[date] = bodyHash;\n\n if (allElided) continue;\n\n const needsSmartContext =\n settings.memoryMode === \"smart\" || settings.importNativeMemories === \"smart\";\n\n // The memory pass runs when the day changed, when forced, or when\n // the last pass for this exact content didn't complete cleanly —\n // a sync that stored the transcript but failed mid-memory-write\n // self-heals on the next run instead of being frozen out by the\n // unchanged-day skip (Cursor review on PR #1458).\n const memoryPassComplete =\n previousState?.memoryDayHashes?.[date] === bodyHash;\n if (\n settings.memoryMode !== \"off\" &&\n (changed || options.forceMemories === true || !memoryPassComplete)\n ) {\n if (!deps.memoryGen) {\n summary.warnings.push(\n `${connector.id}: memoryMode is '${settings.memoryMode}' but no extraction engine is available in this context — transcripts synced, memories skipped`,\n );\n } else {\n // The whole memory pass (extraction, fact writes, digest) is\n // warn-and-retry rather than abort: the transcript is already\n // stored, and aborting here would leave any stale completion\n // record from an earlier clean run in place to mask the\n // failure (Kilo review on PR #1458 for the digest case; fact\n // writes share the same failure class). A clean pass records\n // completion; anything else clears it so the next sync\n // re-runs the day.\n let passClean = false;\n try {\n const corroboration = needsSmartContext\n ? await buildCorroborationContext(connector.id, date, deps)\n : undefined;\n const dayMemoryGen: WearableMemoryGenDeps = {\n ...deps.memoryGen,\n ...(corroboration !== undefined ? { corroboration } : {}),\n };\n const generated = await generateWearableMemories(\n connector.id,\n date,\n cleaned.conversations,\n settings,\n registry,\n dayMemoryGen,\n );\n summary.memoriesCreated += generated.created;\n summary.memoriesPromoted += generated.promoted;\n summary.memoriesDemoted += generated.demoted;\n summary.memoriesSkipped += generated.skipped;\n summary.memoriesBlocked += generated.tombstoneBlocked;\n summary.warnings.push(...generated.warnings);\n // Degraded-but-complete passes (e.g. judge unavailable) still\n // record completion — only an aborted extraction should force\n // the day to re-run on the next sync (Cursor review on PR\n // #1462).\n passClean = generated.completed;\n const { digestEnabled } = config;\n if (digestEnabled) {\n const wrote = await writeDailyDigestMemory(\n connector.id,\n date,\n cleaned.conversations,\n settings,\n registry,\n deps.memoryGen.writer,\n );\n if (wrote) summary.memoriesCreated += 1;\n }\n } catch (err) {\n passClean = false;\n summary.warnings.push(\n `${connector.id}: memory pass failed for ${date}: ${describeErrorForOperator(err)} — retries on the next sync`,\n );\n }\n if (passClean) {\n memoryDayHashes[date] = bodyHash;\n } else {\n failedMemoryDays.push(date);\n }\n }\n } else if (settings.memoryMode !== \"off\" && memoryPassComplete) {\n // Carry the completion record forward for unchanged days.\n memoryDayHashes[date] = bodyHash;\n }\n }\n\n if (\n settings.importNativeMemories !== \"off\" &&\n typeof connector.fetchNativeMemories === \"function\"\n ) {\n if (!deps.memoryGen) {\n summary.warnings.push(\n `${connector.id}: importNativeMemories is enabled but no memory writer is available in this context`,\n );\n } else {\n const alreadyImported = new Set(\n previousState?.importedNativeMemoryIds ?? [],\n );\n // Native memories carry no day, so same-day cross-device\n // corroboration does not apply to them — scoring a provider fact\n // against an arbitrary day's tokens would be wrong-day evidence\n // (Cursor review on PR #1462). They keep only the day-independent\n // existing-memory support boost.\n const nativeCorroboration =\n settings.importNativeMemories === \"smart\"\n ? {\n otherSourceDayTokens: new Map<string, Set<string>>(),\n existingMemories: deps.listSupportMemories\n ? await deps.listSupportMemories()\n : [],\n }\n : undefined;\n const nativeMemoryGen: WearableMemoryGenDeps = {\n ...deps.memoryGen,\n ...(nativeCorroboration !== undefined\n ? { corroboration: nativeCorroboration }\n : {}),\n };\n let cursor: string | null | undefined = undefined;\n const seenNativeCursors = new Set<string>();\n for (let page = 0; page < PAGE_SAFETY_CEILING; page++) {\n const result = await connector.fetchNativeMemories({\n cursor,\n signal: options.signal,\n });\n const imported = await importNativeMemories(\n connector.id,\n result.memories,\n alreadyImported,\n settings,\n nativeMemoryGen,\n );\n summary.warnings.push(...imported.warnings);\n summary.nativeMemoriesImported += imported.imported;\n summary.memoriesBlocked += imported.tombstoneBlocked;\n importedNativeIds.push(...imported.importedIds);\n for (const id of imported.importedIds) alreadyImported.add(id);\n if (!result.nextCursor) break;\n if (seenNativeCursors.has(result.nextCursor)) {\n summary.warnings.push(\n `${connector.id}: provider pagination repeated cursor during native-memory import — stopped to avoid an infinite loop; remaining items import on the next sync`,\n );\n break;\n }\n seenNativeCursors.add(result.nextCursor);\n cursor = result.nextCursor;\n if (page === PAGE_SAFETY_CEILING - 1) {\n summary.warnings.push(\n `${connector.id}: stopped native-memory import at the ${PAGE_SAFETY_CEILING}-page safety ceiling — remaining items import on the next sync`,\n );\n }\n }\n }\n }\n\n const wroteAnything =\n summary.transcriptsWritten.length > 0 ||\n summary.memoriesCreated > 0 ||\n summary.memoriesPromoted > 0 ||\n summary.memoriesDemoted > 0 ||\n summary.memoriesBlocked > 0 ||\n summary.nativeMemoriesImported > 0;\n if (wroteAnything && deps.afterWrites) {\n try {\n await deps.afterWrites();\n } catch (err) {\n summary.warnings.push(\n `search reindex failed (writes are stored and will index on the next update): ${describeErrorForOperator(err)}`,\n );\n }\n }\n\n // Watermark advances only now — after transcript writes, memory\n // writes, and native imports all succeeded.\n syncState = updateSourceSyncState(syncState, connector.id, {\n syncedAt: now.toISOString(),\n days: dates,\n dayHashes,\n memoryDayHashes,\n clearMemoryDays: failedMemoryDays,\n importedNativeMemoryIds: importedNativeIds,\n });\n // New same-day evidence invalidates OTHER sources' memory-pass\n // completion for the days this source just (re)wrote: their next\n // sync re-scores with this transcript available, and the promotion\n // path upgrades earlier borderline writes in place (Cursor review on\n // PR #1462).\n if (summary.transcriptsWritten.length > 0) {\n const cleared: typeof syncState.sources = {};\n for (const [otherId, otherState] of Object.entries(syncState.sources)) {\n if (otherId === connector.id || otherState.memoryDayHashes === undefined) {\n cleared[otherId] = otherState;\n continue;\n }\n const memoryDays = { ...otherState.memoryDayHashes };\n let touched = false;\n for (const date of summary.transcriptsWritten) {\n if (date in memoryDays) {\n delete memoryDays[date];\n touched = true;\n }\n }\n cleared[otherId] = touched\n ? { ...otherState, memoryDayHashes: memoryDays }\n : otherState;\n }\n syncState = { version: 1, sources: cleared };\n }\n await saveSyncState(deps.memoryDir, syncState);\n\n return summary;\n}\n\n/**\n * Assemble smart-mode corroboration evidence: other sources' same-day\n * transcript tokens + existing active memories. The memory list loads\n * fresh per day (not per run) so facts written on earlier days of a\n * multi-day backfill are visible as support evidence on later days —\n * the underlying readAllMemories is cached in storage and invalidated\n * by writes, so the per-day refresh is cheap (Cursor review on PR\n * #1462).\n */\nasync function buildCorroborationContext(\n sourceId: string,\n date: string,\n deps: WearableSyncDeps,\n): Promise<CorroborationContext> {\n const otherSourceDayTokens = new Map<string, Set<string>>();\n if (deps.readOtherSourceDayBodies) {\n const bodies = await deps.readOtherSourceDayBodies(date, sourceId);\n for (const [otherSource, body] of bodies) {\n otherSourceDayTokens.set(otherSource, tokenizeDayBody(body));\n }\n }\n const existingMemories = deps.listSupportMemories\n ? await deps.listSupportMemories()\n : [];\n return { otherSourceDayTokens, existingMemories };\n}\n\nexport function defaultTimezone(): string {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n } catch {\n return \"UTC\";\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lifecycle/tombstones.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// lifecycle/tombstones.ts — Tombstone store + non-resurrection invariant\n// (issue #1579)\n//\n// A \"tombstone\" records that a fact has been retired (corrected, superseded,\n// or retracted) so that the SAME fact cannot silently come back to life through\n// any of the five resurrection paths:\n//\n// 1. Re-extraction (session replay, re-observation)\n// 2. Importers (capsule / import-* payloads)\n// 3. Consolidation merges\n// 4. Dreams (REM re-derivation)\n// 5. Pattern reinforcement (duplicate promotion)\n//\n// The invariant is enforced at the SINGLE storage persist path\n// (`StorageManager.writeMemory` — the same chokepoint that records catalog\n// writes, issue #1522). Every write path funnels through it, so paths (a)–(e)\n// are blocked WITHOUT per-path code.\n//\n// File-first / rebuildable (the repo's storage philosophy): the JSONL at\n// `<stateDir>/tombstones.jsonl` is a cache of truth. The authoritative sources\n// are the memory files themselves (status: superseded/retracted) and the\n// `corrections/` records. `rebuildTombstonesFromStorage` reconstructs the\n// JSONL from those sources.\n//\n// Design rules honored (issue #1579 design section):\n// - Hash `rawContent` exactly as the dedup index does — one helper\n// (`ContentHashIndex.computeHash` / `normalizeContent`), never a second\n// (checklist §13; rule 23).\n// - Append-only log — never rewrite history; a later `kind: \"revocation\"`\n// entry re-allows (rule 25).\n// - Instance-scoped, never module-level (rule 11).\n// - Namespace-scoped — a tombstone in namespace A never blocks namespace B\n// (rule 42).\n// - Serialized appends with rejection recovery (rule 40).\n// - Never silent drop — a blocked write lands as pending_review + blockedBy\n// (rule 34).\n// - Do NOT register a blocked fact as an active dedup/index entry (rule 44).\n// - Off = pre-feature behavior for rollback safety (rule 30).\n// - Semantic tier (4) ships dark, off by default (rule 48).\n// ---------------------------------------------------------------------------\n\nimport { serializeMutations, withHeldFileLock } from \"../utils/serialize-mutations.js\";\n\n/** Why a tombstone was emitted. */\nexport type TombstoneReason =\n | \"correction\"\n | \"supersession\"\n | \"retraction\"\n | \"contradiction_resolution\";\n\n/** Who emitted the tombstone. */\nexport type TombstoneCreatedBy =\n | \"user_correction\"\n | \"contradiction_resolution\"\n | \"supersession\"\n | \"chat\";\n\n/** Tier that matched on lookup. */\nexport type TombstoneMatchTier = \"exact\" | \"normalized\" | \"keyed\" | \"semantic\";\n\n/**\n * A single append-only tombstone log entry.\n *\n * `kind: \"tombstone\"` blocks; `kind: \"revocation\"` re-allows. The log is\n * never rewritten — the NEWEST matching entry wins at lookup (rule 25).\n */\nexport interface TombstoneEntry {\n /** Stable tombstone id (`tomb-<ts>-<rand>`). */\n id: string;\n kind: \"tombstone\" | \"revocation\";\n reason: TombstoneReason;\n /** The memory that was retired. */\n sourceMemoryId: string;\n /** sha256 of the retired memory's rawContent (rule 23). */\n contentHash: string;\n /** `ContentHashIndex.normalizeContent(rawContent)` — the pre-hash form. */\n normalizedText: string;\n entityRef?: string;\n /** Structured-attribute supersession key when one existed. */\n supersessionKey?: string;\n /** Stable operation identity for crash-recovery replay. */\n operationKey?: string;\n /** Namespace scope (rule 42). */\n namespace: string;\n createdAt: string;\n createdBy: TombstoneCreatedBy;\n /** For `kind: \"revocation\"`: the tombstone id being revoked. */\n revokes?: string;\n}\n\n/** A positive block decision returned by `TombstoneStore.lookup`. */\nexport interface TombstoneMatch {\n tombstoneId: string;\n matchedTier: TombstoneMatchTier;\n reason: TombstoneReason;\n}\n\n/** Inputs to a lookup. At least one discriminator must be present.\n *\n * Issue #1579 thread Ociag/Oci-W: `supersessionKeys` (plural) lets the write\n * chokepoint check EVERY derived key, not just the first. Emitters register\n * one tombstone per matched key (temporal-supersession, rebuild), so a block\n * can live on any later key; querying only `supersessionKeys[0]` missed it\n * and the retired fact resurrected as active. `supersessionKey` (singular)\n * remains for direct/unit callers; `lookup` checks the union of both. */\nexport interface TombstoneLookupQuery {\n contentHash?: string;\n normalizedText?: string;\n entityRef?: string;\n /** Single supersession key (direct/unit callers). */\n supersessionKey?: string;\n /** All derived supersession keys (write chokepoint). The keyed tier is\n * checked for each; the first active match wins. */\n supersessionKeys?: string[];\n namespace: string;\n}\n\n/** Configuration for a tombstone store instance. */\nexport interface TombstoneStoreOptions {\n enabled: boolean;\n semanticMatch: boolean;\n semanticThreshold: number;\n /** sha256 of raw content — wired to `ContentHashIndex.computeHash`. */\n hashContent: (raw: string) => string;\n /** Normalize raw content — wired to `ContentHashIndex.normalizeContent`. */\n normalizeText: (raw: string) => string;\n /**\n * Optional cosine similarity in [0, 1] for the semantic tier. When\n * undefined or when `semanticMatch` is false, the semantic tier is\n * skipped entirely.\n */\n semanticSimilarity?: (a: string, b: string) => number;\n /**\n * Cross-process write-lock timings for the tombstone JSONL mutation lock\n * (issue #1639). The secure-store append is a read-merge-write (read\n * encrypted → decrypt → concat → re-encrypt → atomic rename), so two\n * Remnic processes appending concurrently can each read the same contents\n * and the last writer drops the other's entry — breaking the\n * non-resurrection invariant. The lock serializes the mutation across\n * processes. When omitted, defaults that fit the short tombstone critical\n * section (mirroring the summary-snapshot lock): staleMs 30s, maxWaitMs 5s,\n * heartbeatMs floor(staleMs/3).\n */\n readonly lockStaleMs?: number;\n readonly lockMaxWaitMs?: number;\n readonly lockHeartbeatMs?: number;\n}\n\n/** Injected file I/O — the StorageManager wires its secure-store-aware\n * implementations so tombstones are encrypted at rest alongside other state.\n * `stat` is optional; when provided the store tracks the file mtime itself and\n * runs a cross-process staleness probe on each access (#1579). */\nexport interface TombstoneFileIo {\n read: (filePath: string) => Promise<string>;\n append: (filePath: string, content: string) => Promise<void>;\n write: (filePath: string, content: string) => Promise<void>;\n stat?: (filePath: string) => { mtimeMs: number };\n}\n\n/** Aggregate stats for `remnic doctor`. */\nexport interface TombstoneStats {\n count: number;\n revoked: number;\n lastAppendAt: string | null;\n corruptedLines: number;\n /** Whether the in-memory index matches the on-disk file (rebuild check). */\n loaded: boolean;\n}\n\nconst TOMBSTONE_PREFIX = \"tomb\";\n\nfunction newTombstoneId(): string {\n return `${TOMBSTONE_PREFIX}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/** Deterministic key for the keyed tier (namespace + entityRef + supersessionKey).\n *\n * The namespace is part of the discriminator so two namespace-scoped stores\n * that share the same backing `tombstones.jsonl` (namespaces disabled, or the\n * same directory used with different namespace configs) cannot overwrite each\n * other's index entries — a later tombstone for namespace B with the same\n * entity/key no longer evicts namespace A's entry (issue #1579 thread Ocs-O). */\nfunction keyedTierKey(namespace: string, entityRef: string, supersessionKey: string): string {\n return `${namespace}\\0${entityRef}\\0${supersessionKey}`;\n}\n\n/**\n * Parse one JSONL line into a TombstoneEntry, validating the discriminated\n * union. Returns `null` for malformed lines (rule 34 — skip with a counter,\n * never crash).\n */\nexport function parseTombstoneLine(line: string): TombstoneEntry | null {\n const trimmed = line.trim();\n if (trimmed.length === 0) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\") return null;\n const e = parsed as Record<string, unknown>;\n if (typeof e.id !== \"string\" || e.id.length === 0) return null;\n if (e.kind !== \"tombstone\" && e.kind !== \"revocation\") return null;\n if (\n e.reason !== \"correction\" &&\n e.reason !== \"supersession\" &&\n e.reason !== \"retraction\" &&\n e.reason !== \"contradiction_resolution\"\n ) {\n return null;\n }\n if (typeof e.sourceMemoryId !== \"string\") return null;\n if (typeof e.contentHash !== \"string\") return null;\n if (typeof e.normalizedText !== \"string\") return null;\n if (typeof e.namespace !== \"string\") return null;\n if (typeof e.createdAt !== \"string\") return null;\n if (\n e.createdBy !== \"user_correction\" &&\n e.createdBy !== \"contradiction_resolution\" &&\n e.createdBy !== \"supersession\" &&\n e.createdBy !== \"chat\"\n ) {\n return null;\n }\n const out: TombstoneEntry = {\n id: e.id,\n kind: e.kind,\n reason: e.reason,\n sourceMemoryId: e.sourceMemoryId,\n contentHash: e.contentHash,\n normalizedText: e.normalizedText,\n namespace: e.namespace,\n createdAt: e.createdAt,\n createdBy: e.createdBy,\n };\n if (typeof e.operationKey === \"string\") out.operationKey = e.operationKey;\n if (typeof e.entityRef === \"string\") out.entityRef = e.entityRef;\n if (typeof e.supersessionKey === \"string\") out.supersessionKey = e.supersessionKey;\n if (typeof e.revokes === \"string\") out.revokes = e.revokes;\n return out;\n}\n\n/**\n * The instance-scoped tombstone store. One per StorageManager (rule 11),\n * namespace-scoped via `namespace` + the on-disk path (rule 42).\n *\n * The in-memory index is built lazily on first access and invalidated on\n * append + by the StorageManager's cache-invalidation chokepoint (rule 25).\n */\nexport class TombstoneStore {\n private entries: TombstoneEntry[] = [];\n /** contentHash → tombstone id (newest) — exact tier. */\n private readonly byHash = new Map<string, string>();\n /** normalizedText → tombstone id (newest) — normalized tier. */\n private readonly byNormalized = new Map<string, string>();\n /** entityRef\\0supersessionKey → tombstone id (newest) — keyed tier. */\n private readonly byKey = new Map<string, string>();\n /** Tombstone ids that have a later revocation entry. */\n private readonly revokedIds = new Set<string>();\n /** id → entry index (newest only, for revocation lookups). */\n private readonly byId = new Map<string, TombstoneEntry>();\n private loaded = false;\n private loadPromise: Promise<void> | null = null;\n private corruptedLines = 0;\n private lastAppendAt: string | null = null;\n /**\n * Last-seen mtime of the JSONL file (cross-process staleness probe, #1579).\n * Tracked inside the store so the StorageManager wiring stays thin. When the\n * file's mtime advances past this value between accesses, the in-memory index\n * is stale (a peer process appended) and is reloaded before the next lookup.\n */\n private fileMtimeMs = 0;\n\n constructor(\n private readonly filePath: string,\n private readonly namespace: string,\n private readonly options: TombstoneStoreOptions,\n private readonly io: TombstoneFileIo,\n ) {\n this.lockStaleMs = this.options.lockStaleMs ?? 30_000;\n this.lockMaxWaitMs = this.options.lockMaxWaitMs ?? 5_000;\n this.lockHeartbeatMs =\n this.options.lockHeartbeatMs ?? Math.max(100, Math.floor(this.lockStaleMs / 3));\n }\n\n /**\n * Resolved cross-process write-lock timings (issue #1639). Defaults mirror\n * the summary-snapshot lock: a tombstone append critical section is\n * sub-second, so a stale lock older than 30s is a crashed holder; bounded\n * acquisition gives up after 5s and strict-fails rather than clobbering a\n * concurrent writer (which would reintroduce the lost-write race this lock\n * closes).\n */\n private readonly lockStaleMs: number;\n private readonly lockMaxWaitMs: number;\n private readonly lockHeartbeatMs: number;\n\n /**\n * Sibling advisory lockfile: `<tombstones>.jsonl` → `<tombstones>.lock`.\n * Shared by append, revoke, and rebuild so every JSONL mutation serializes\n * against the others across processes (issue #1639: \"shared with rebuilds\").\n */\n private get lockPath(): string {\n return this.filePath.replace(/\\.jsonl$/i, \"\") + \".lock\";\n }\n\n /** Lazy load + build the in-memory index. Idempotent. */\n async load(): Promise<void> {\n if (this.loaded) return;\n if (this.loadPromise) return this.loadPromise;\n this.loadPromise = this.loadInternal();\n try {\n await this.loadPromise;\n } finally {\n this.loadPromise = null;\n }\n }\n\n private async loadInternal(): Promise<void> {\n // Record the file's mtime so the staleness probe does not immediately\n // invalidate on the next access (fileMtimeMs starts at 0; without this,\n // any non-zero mtime would trigger a spurious reload). Done before the\n // read so an ENOENT still records 0.\n this.recordFileMtime();\n let raw: string;\n try {\n raw = await this.io.read(this.filePath);\n } catch (err) {\n // ENOENT is fine — fresh store. Other errors leave the store empty\n // rather than crashing the write path (rule 34 spirit: degrade to\n // \"no tombstones known\" rather than blocking all writes).\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== \"ENOENT\") {\n // Swallow — the write path must not crash on a corrupt tombstone file.\n // Rebuild will repair on the next doctor/maintenance run.\n }\n this.loaded = true;\n return;\n }\n this.resetIndex();\n let corrupted = 0;\n for (const line of raw.split(\"\\n\")) {\n const entry = parseTombstoneLine(line);\n if (!entry) {\n if (line.trim().length > 0) corrupted += 1;\n continue;\n }\n this.indexEntry(entry);\n }\n this.corruptedLines = corrupted;\n this.loaded = true;\n }\n\n private resetIndex(): void {\n this.entries = [];\n this.byHash.clear();\n this.byNormalized.clear();\n this.byKey.clear();\n this.revokedIds.clear();\n this.byId.clear();\n }\n\n /**\n * Index an entry. Later entries (higher array index = newer createdAt)\n * OVERRIDE earlier ones at each tier key — the newest matching entry wins\n * (rule 25). Revocation entries mark their target as revoked.\n */\n private indexEntry(entry: TombstoneEntry): void {\n this.entries.push(entry);\n this.byId.set(entry.id, entry);\n if (entry.createdAt > (this.lastAppendAt ?? \"\")) {\n this.lastAppendAt = entry.createdAt;\n }\n if (entry.kind === \"revocation\") {\n if (entry.revokes) this.revokedIds.add(entry.revokes);\n return;\n }\n // Tombstone entries populate the lookup maps. The namespace is part of\n // every discriminator key (issue #1579 thread Ocs-O): when two\n // namespace-scoped stores share the same backing file, a later tombstone\n // for namespace B with identical content must NOT evict namespace A's map\n // entry — otherwise A's lookup finds B's id, rejects it on namespace\n // mismatch, and misses its own still-active tombstone (resurrection).\n const ns = entry.namespace;\n if (entry.contentHash) this.byHash.set(`${ns}\\0${entry.contentHash}`, entry.id);\n if (entry.normalizedText) this.byNormalized.set(`${ns}\\0${entry.normalizedText}`, entry.id);\n if (entry.entityRef && entry.supersessionKey) {\n this.byKey.set(keyedTierKey(ns, entry.entityRef, entry.supersessionKey), entry.id);\n }\n }\n\n /** Invalidate the in-memory cache (rule 25). The next access reloads. */\n invalidate(): void {\n this.loaded = false;\n this.loadPromise = null;\n this.resetIndex();\n }\n\n /**\n * Cross-process staleness probe (#1579). If the file's mtime advanced since\n * the last load/own-write, a peer process appended and our in-memory index is\n * stale — invalidate AND reload in place so the next lookup sees the new\n * entries. Best-effort: a stat error is swallowed (the loaded index is\n * retained rather than crashing the write path). No-op when no `stat` was\n * injected or when the mtime is unchanged. After our own append/revoke/rebuild\n * `markWritten` records the new mtime, so this probe does not fire for our\n * own writes (only for peer-process appends).\n */\n async ensureFreshAgainstDisk(): Promise<void> {\n if (!this.io.stat) return;\n let mtimeMs: number;\n try {\n mtimeMs = Math.floor(this.io.stat(this.filePath).mtimeMs);\n } catch {\n return; // ENOENT / permission — keep the loaded index (fail-open).\n }\n if (mtimeMs === this.fileMtimeMs && this.loaded) return;\n this.fileMtimeMs = mtimeMs;\n this.invalidate();\n await this.load();\n }\n\n /**\n * Record the file mtime after THIS process writes (append / revoke / rebuild)\n * so `ensureFreshAgainstDisk` does not treat our own write as a peer append\n * and throw away the just-updated in-memory index (#1579 — avoids a needless\n * invalidate+reload in the hot write path).\n */\n private markWritten(): void {\n this.recordFileMtime();\n }\n\n private recordFileMtime(): void {\n if (!this.io.stat) return;\n try {\n this.fileMtimeMs = Math.floor(this.io.stat(this.filePath).mtimeMs);\n } catch {\n // ENOENT — fresh store with no file yet; mtime stays at its current value.\n }\n }\n\n /**\n * Append a tombstone entry. Serialized via `serializeMutations` keyed by\n * file path so concurrent appends do not interleave, with rejection recovery\n * (rule 40 — a single failed append never poisons the chain). The in-memory\n * index is updated AFTER the durable append succeeds.\n *\n * Returns the new tombstone id.\n */\n async appendTombstone(input: {\n reason: TombstoneReason;\n createdBy: TombstoneCreatedBy;\n sourceMemoryId: string;\n rawContent: string;\n entityRef?: string;\n supersessionKey?: string;\n operationKey?: string;\n createdAt?: string;\n /**\n * Pre-computed canonical contentHash from the retired memory's frontmatter.\n * When provided, used directly so the tombstone's exact tier matches the\n * hash writeMemory computes on re-extraction (issue #1579 review: cited\n * facts must not slip past the chokepoint because the emitter hashed the\n * citation-annotated body instead of the canonical source).\n */\n contentHash?: string;\n }): Promise<string> {\n await this.load();\n const createdAt = input.createdAt ?? new Date().toISOString();\n const id = newTombstoneId();\n const entry: TombstoneEntry = {\n id,\n kind: \"tombstone\",\n reason: input.reason,\n sourceMemoryId: input.sourceMemoryId,\n contentHash: input.contentHash ?? this.options.hashContent(input.rawContent),\n normalizedText: this.options.normalizeText(input.rawContent),\n ...(input.entityRef ? { entityRef: input.entityRef } : {}),\n ...(input.supersessionKey ? { supersessionKey: input.supersessionKey } : {}),\n ...(input.operationKey ? { operationKey: input.operationKey } : {}),\n namespace: this.namespace,\n createdAt,\n createdBy: input.createdBy,\n };\n await this.serializeAppend(entry);\n this.indexEntry(entry);\n this.markWritten();\n return id;\n }\n\n /**\n * Append a revocation entry referencing `tombstoneId`. The log is\n * append-only; lookup treats a revoked tombstone as re-allowed (rule 25).\n */\n async revoke(tombstoneId: string, createdBy: TombstoneCreatedBy): Promise<string> {\n await this.load();\n const createdAt = new Date().toISOString();\n const id = newTombstoneId();\n const entry: TombstoneEntry = {\n id,\n kind: \"revocation\",\n reason: \"correction\",\n sourceMemoryId: \"\",\n contentHash: \"\",\n normalizedText: \"\",\n namespace: this.namespace,\n createdAt,\n createdBy,\n revokes: tombstoneId,\n };\n await this.serializeAppend(entry);\n this.indexEntry(entry);\n this.markWritten();\n return id;\n }\n\n private serializeAppend(entry: TombstoneEntry): Promise<void> {\n const line = JSON.stringify(entry) + \"\\n\";\n // serializeMutations (in-process, rule 40 rejection recovery) wraps the\n // cross-process lock: within one process appends are queued, and across\n // processes the advisory lockfile serializes the read-merge-write so two\n // Remnic processes appending concurrently cannot drop each other's entry\n // (issue #1639). Under the lock we re-sync the in-memory index against\n // disk first, so indexEntry builds on the peer's just-appended entries.\n return serializeMutations(`tombstone:${this.filePath}`, () =>\n this.withWriteLock(async () => {\n await this.ensureFreshAgainstDisk();\n await this.io.append(this.filePath, line);\n }),\n );\n }\n\n /**\n * Acquire the cross-process tombstone write lock and run `task` under it\n * (issue #1639). Strict-fail on acquisition timeout: the secure-store append\n * is a read-merge-write, so a best-effort unlocked run would clobber a\n * concurrent writer — reintroducing the lost-write race this lock closes.\n * The thrown error surfaces to the caller (rule 34: never silently drop);\n * rebuild recovers the tombstone on the next maintenance cycle.\n *\n * In-process callers are already serialized by serializeMutations (one key\n * for append/revoke/rebuild), so this lock only contends across processes —\n * the single-process daemon (default deployment) acquires it immediately.\n */\n private withWriteLock<T>(task: () => Promise<T>): Promise<T> {\n return withHeldFileLock(\n this.lockPath,\n {\n staleMs: this.lockStaleMs,\n maxWaitMs: this.lockMaxWaitMs,\n heartbeatMs: this.lockHeartbeatMs,\n },\n async (acquired) => {\n if (!acquired) {\n throw new Error(\n \"could not acquire tombstone write lock (contention timeout or filesystem error)\",\n );\n }\n return task();\n },\n );\n }\n\n /**\n * Look up whether `query` is blocked by an active (non-revoked) tombstone.\n * Tiers are checked in order: exact (contentHash) → normalized → keyed →\n * semantic (off by default). Returns the first active match, or `null`.\n *\n * Namespace isolation (rule 42): only entries with `namespace === query.namespace`\n * match. The store is per-namespace, so this is belt-and-suspenders.\n */\n lookup(query: TombstoneLookupQuery): TombstoneMatch | null {\n // We intentionally do NOT await load() here — lookup is called from the\n // hot write path. Callers MUST ensure the store is loaded before lookup\n // (StorageManager does this in ensureTombstoneStoreLoaded). If not loaded,\n // lookup returns null (fail-open: a missing tombstone check is preferable\n // to crashing every write — see rule 34 / the \"degrade gracefully\" note).\n if (!this.options.enabled) return null;\n if (!this.loaded) return null;\n\n // Tier 1: exact contentHash. Namespace is part of the map key (thread\n // Ocs-O), so the lookup finds only this namespace's tombstone even when\n // the backing file is shared. The namespace equality check below is kept\n // as defense-in-depth.\n const ns = query.namespace;\n if (query.contentHash) {\n const id = this.byHash.get(`${ns}\\0${query.contentHash}`);\n if (id && !this.revokedIds.has(id)) {\n const entry = this.byId.get(id);\n if (entry && entry.namespace === ns) {\n return { tombstoneId: id, matchedTier: \"exact\", reason: entry.reason };\n }\n }\n }\n // Tier 2: normalized text.\n if (query.normalizedText) {\n const id = this.byNormalized.get(`${ns}\\0${query.normalizedText}`);\n if (id && !this.revokedIds.has(id)) {\n const entry = this.byId.get(id);\n if (entry && entry.namespace === ns) {\n return { tombstoneId: id, matchedTier: \"normalized\", reason: entry.reason };\n }\n }\n }\n // Tier 3: keyed (entityRef + supersessionKey). Issue #1579 thread\n // Ociag/Oci-W: check EVERY supplied key, not just the first — emitters\n // append one tombstone per matched supersession key, so the active block\n // can live on any later key. The union of `supersessionKey` (singular,\n // direct callers) and `supersessionKeys` (array, write chokepoint) is\n // checked; the first active match wins (tiers are equality-based, so\n // ordering across keys does not affect correctness).\n if (query.entityRef) {\n const keysToCheck: string[] = [];\n if (query.supersessionKey) keysToCheck.push(query.supersessionKey);\n if (query.supersessionKeys) {\n for (const k of query.supersessionKeys) {\n if (!keysToCheck.includes(k)) keysToCheck.push(k);\n }\n }\n for (const key of keysToCheck) {\n const id = this.byKey.get(keyedTierKey(ns, query.entityRef, key));\n if (id && !this.revokedIds.has(id)) {\n const entry = this.byId.get(id);\n if (entry && entry.namespace === ns) {\n return { tombstoneId: id, matchedTier: \"keyed\", reason: entry.reason };\n }\n }\n }\n }\n // Tier 4: semantic (off by default, rule 48).\n if (this.options.semanticMatch && this.options.semanticSimilarity && query.normalizedText) {\n const threshold = this.options.semanticThreshold;\n let best: { id: string; reason: TombstoneReason; score: number } | null = null;\n for (const entry of this.entries) {\n if (entry.kind !== \"tombstone\") continue;\n if (entry.namespace !== query.namespace) continue;\n if (this.revokedIds.has(entry.id)) continue;\n if (!entry.normalizedText) continue;\n const score = this.options.semanticSimilarity(query.normalizedText, entry.normalizedText);\n if (score >= threshold && (!best || score > best.score)) {\n best = { id: entry.id, reason: entry.reason, score };\n }\n }\n if (best) {\n return { tombstoneId: best.id, matchedTier: \"semantic\", reason: best.reason };\n }\n }\n return null;\n }\n async hasExactEntry(input: {\n sourceMemoryId: string;\n contentHash?: string;\n entityRef?: string;\n supersessionKey?: string;\n createdAt?: string;\n operationKey?: string;\n }): Promise<boolean> {\n if (!this.options.enabled) return false;\n await this.ensureFreshAgainstDisk();\n await this.load();\n return this.entries.some(\n (entry) =>\n entry.kind === \"tombstone\" &&\n !this.revokedIds.has(entry.id) &&\n entry.namespace === this.namespace &&\n entry.sourceMemoryId === input.sourceMemoryId &&\n (input.contentHash === undefined || entry.contentHash === input.contentHash) &&\n (input.entityRef === undefined || entry.entityRef === input.entityRef) &&\n (input.supersessionKey === undefined || entry.supersessionKey === input.supersessionKey) &&\n (input.createdAt === undefined || entry.createdAt === input.createdAt) &&\n (input.operationKey === undefined || entry.operationKey === input.operationKey),\n );\n }\n\n\n /** Aggregate stats for the doctor / x-ray surfaces. */\n stats(): TombstoneStats {\n let active = 0;\n for (const entry of this.entries) {\n if (entry.kind === \"tombstone\" && !this.revokedIds.has(entry.id)) active += 1;\n }\n return {\n count: active,\n revoked: this.revokedIds.size,\n lastAppendAt: this.lastAppendAt,\n corruptedLines: this.corruptedLines,\n loaded: this.loaded,\n };\n }\n\n /** Read-only snapshot of all entries (for rebuild + tests). */\n snapshot(): readonly TombstoneEntry[] {\n return this.entries;\n }\n\n /**\n * Rebuild the in-memory index + JSONL from the supplied retired-memory\n * records + existing entries (preserving revocations). Byte-stable: entries\n * are sorted by (createdAt, id) before write (rule 38).\n *\n * Returns the count of tombstone entries written.\n */\n async rebuild(retiredMemories: ReadonlyArray<RetiredMemoryRecord>): Promise<number> {\n // The full read-merge-write runs UNDER the cross-process lock (issue #1639,\n // cursor/codex review): we ensureFreshAgainstDisk() first so the in-memory\n // index reflects any peer append/revoke that landed while we waited for the\n // lock, THEN compute the payload. Rebuild rewrites the entire JSONL, so a\n // payload built from a stale this.entries would overwrite a peer's just-\n // written entry and resurrect a retired fact — the exact invariant this\n // store exists to enforce. serializeMutations (in-process) wraps the\n // cross-process lock; the key is unified with append/revoke so they cannot\n // interleave in-process either.\n const rebuiltCount = await serializeMutations(`tombstone:${this.filePath}`, () =>\n this.withWriteLock(async () => {\n await this.ensureFreshAgainstDisk();\n // Preserve existing revocations (all namespaces — ids are globally\n // unique) so a rebuild does not silently un-revoke.\n const existingRevocations = this.entries.filter((e) => e.kind === \"revocation\");\n // Preserve tombstone entries from OTHER namespaces when the backing\n // file is shared (issue #1579 thread Oc2MJ). rebuild rewrites the\n // entire file; without preserving foreign entries, rebuilding\n // namespace A would silently delete namespace B's tombstones, allowing\n // resurrection in B.\n const foreignTombstones = this.entries.filter(\n (e) => e.kind === \"tombstone\" && e.namespace !== this.namespace,\n );\n // Reuse existing tombstone ids for source-equivalent entries so a prior\n // revocation (which references the tombstone id) survives rebuild —\n // minting fresh ids would orphan the revocation and silently un-revoke.\n // Issue #1579 thread Oci-T: key the reuse map by (sourceMemoryId,\n // supersessionKey). See the append path above for the full rationale.\n const existingBySource = new Map<string, string>();\n for (const e of this.entries) {\n if (e.kind === \"tombstone\") {\n existingBySource.set(`${e.sourceMemoryId}\\u{0000}${e.supersessionKey ?? \"\"}`, e.id);\n }\n }\n const rebuilt: TombstoneEntry[] = retiredMemories.map((m) => ({\n id:\n existingBySource.get(`${m.memoryId}\\u{0000}${m.supersessionKey ?? \"\"}`) ??\n newTombstoneId(),\n kind: \"tombstone\" as const,\n reason: m.reason,\n sourceMemoryId: m.memoryId,\n contentHash: m.contentHash ?? this.options.hashContent(m.rawContent),\n normalizedText: this.options.normalizeText(m.rawContent),\n ...(m.entityRef ? { entityRef: m.entityRef } : {}),\n ...(m.supersessionKey ? { supersessionKey: m.supersessionKey } : {}),\n namespace: this.namespace,\n createdAt: m.createdAt,\n createdBy: m.createdBy,\n }));\n // Sort deterministically (rule 38): createdAt, then id for stability.\n rebuilt.sort((a, b) =>\n a.createdAt === b.createdAt\n ? a.id < b.id ? -1 : a.id > b.id ? 1 : 0\n : a.createdAt < b.createdAt ? -1 : 1,\n );\n const all = [...rebuilt, ...existingRevocations, ...foreignTombstones].sort((a, b) =>\n a.createdAt === b.createdAt\n ? a.id < b.id ? -1 : a.id > b.id ? 1 : 0\n : a.createdAt < b.createdAt ? -1 : 1,\n );\n const serialized =\n all.map((e) => JSON.stringify(e)).join(\"\\n\") + (all.length > 0 ? \"\\n\" : \"\");\n await this.io.write(this.filePath, serialized);\n // Update the in-memory index under the same lock so it stays consistent\n // with the file we just wrote (no window for a lookup to see a stale\n // index that misses a just-rebuilt entry).\n this.resetIndex();\n for (const entry of all) this.indexEntry(entry);\n this.corruptedLines = 0;\n this.loaded = true;\n this.markWritten();\n return rebuilt.length;\n }),\n );\n return rebuiltCount;\n }\n}\n\n/** A retired memory projected into the shape `TombstoneStore.rebuild` consumes. */\nexport interface RetiredMemoryRecord {\n memoryId: string;\n rawContent: string;\n entityRef?: string;\n supersessionKey?: string;\n reason: TombstoneReason;\n createdBy: TombstoneCreatedBy;\n createdAt: string;\n /** Canonical contentHash from the retired memory's frontmatter (#1579). */\n contentHash?: string;\n}\n\n/**\n * Project a corpus of memories into the retired-memory records `rebuild`\n * consumes. Pure (no I/O) so the StorageManager wiring stays thin (#1579,\n * #1520 god-file ratchet). Only superseded / forgotten / rejected FACTS\n * participate — entities, questions, and artifacts have their own lifecycle\n * (issue pitfall). The supersession key is derived from structured attributes\n * via the injected helper so there is one keyed-tier definition (rule 23).\n */\nexport function collectRetiredMemoriesForRebuild(\n memories: ReadonlyArray<{\n frontmatter: {\n id: string;\n status?: string;\n category?: string;\n contentHash?: string;\n entityRef?: string;\n structuredAttributes?: Record<string, string>;\n updated?: string;\n created?: string;\n };\n content: string;\n }>,\n deps: {\n /** Strip citation annotations from the body before hashing/normalizing. */\n stripCitation: (text: string) => string;\n /** Derive the keyed-tier supersession key (one helper, rule 23). */\n supersessionKeysForFact: (spec: {\n entityRef?: string;\n structuredAttributes?: Record<string, string>;\n }) => string[];\n },\n): RetiredMemoryRecord[] {\n const retired: RetiredMemoryRecord[] = [];\n for (const m of memories) {\n const status = m.frontmatter.status;\n if (status !== \"superseded\" && status !== \"rejected\" && status !== \"forgotten\") continue;\n if (m.frontmatter.category !== \"fact\") continue;\n const reason: TombstoneReason =\n status === \"superseded\" ? \"supersession\" : status === \"forgotten\" ? \"retraction\" : \"correction\";\n const createdBy: TombstoneCreatedBy =\n reason === \"supersession\" ? \"supersession\" : \"user_correction\";\n const entityRef = m.frontmatter.entityRef;\n const keys =\n entityRef && m.frontmatter.structuredAttributes\n ? deps.supersessionKeysForFact({\n entityRef,\n structuredAttributes: m.frontmatter.structuredAttributes,\n })\n : [];\n // Issue #1579 thread Ocgjz: emit one record per matched supersession key\n // (not just keys[0]) so rebuild reproduces the same keyed tombstones as\n // live temporal-supersession (which now appends one per key — thread ObteS).\n // Without this, a rebuild would under-rebuild the JSONL and keyed-tier\n // blocks would disappear until rediscovered.\n const keysToEmit = keys.length > 0 ? keys : [undefined];\n for (const key of keysToEmit) {\n retired.push({\n memoryId: m.frontmatter.id,\n rawContent: deps.stripCitation(m.content),\n contentHash: m.frontmatter.contentHash,\n ...(entityRef ? { entityRef } : {}),\n ...(key ? { supersessionKey: key } : {}),\n reason,\n createdBy,\n createdAt: m.frontmatter.updated || m.frontmatter.created || new Date().toISOString(),\n });\n }\n }\n return retired;\n}\n\n/**\n * One-shot write-time resurrection gate (issue #1579 / #2213). Looks up the\n * fact's identity in the tombstone store and, on a match, downgrades the\n * frontmatter to `pending_review` + `blockedBy` IN PLACE. ADD-ONLY: an\n * already-blocked or terminal-status record is left untouched. Pure over its\n * inputs (no I/O beyond the injected store) so both `writeMemory`'s gate and\n * the chunk-repair re-gate share one lookup + apply.\n */\nexport function applyTombstoneResurrectionGate(\n store: Pick<TombstoneStore, \"lookup\">,\n fm: {\n id: string;\n status?: string;\n blockedBy?: string;\n tombstoneBlockTier?: TombstoneMatchTier;\n entityRef?: string;\n contentHash?: string;\n },\n query: {\n normalizedText: string;\n supersessionKeys: string[];\n namespace: string;\n },\n): TombstoneMatch | null {\n if (fm.status === \"pending_review\" && fm.blockedBy) return null;\n if (!(fm.status === undefined || fm.status === \"active\" || fm.status === \"pending_review\")) return null;\n const match = store.lookup({\n contentHash: fm.contentHash,\n normalizedText: query.normalizedText,\n ...(fm.entityRef ? { entityRef: fm.entityRef } : {}),\n ...(query.supersessionKeys.length > 0 ? { supersessionKeys: query.supersessionKeys } : {}),\n namespace: query.namespace,\n });\n if (!match) return null;\n fm.status = \"pending_review\";\n fm.blockedBy = match.tombstoneId;\n fm.tombstoneBlockTier = match.matchedTier;\n return match;\n}\n\n/**\n * Build the live-emission tombstone inputs for a single retired FACT — one\n * input per derived supersession key (or a single keyless record when no\n * structured attributes are present). Pure (no I/O) so the emitters in\n * `StorageManager.supersedeMemory` (contradiction) and `forgetMemory`\n * (retraction) stay thin (#1579, #1520 god-file ratchet). Mirrors the\n * temporal-supersession emitter and `collectRetiredMemoriesForRebuild` so\n * every retire path emits the same keyed-tombstone shape (issue #1579\n * threads Oci-Y / OchiF: without per-key tombstones, a paraphrased\n * re-observation missed the keyed tier and the fact resurrected active).\n */\nexport function buildRetiredFactTombstoneInputs(\n memory: {\n id: string;\n content: string;\n contentHash?: string;\n entityRef?: string;\n structuredAttributes?: Record<string, string>;\n },\n opts: {\n reason: TombstoneReason;\n createdBy: TombstoneCreatedBy;\n createdAt: string;\n supersessionKeysForFact: (spec: {\n entityRef?: string;\n structuredAttributes?: Record<string, string>;\n }) => string[];\n },\n): Array<{\n reason: TombstoneReason;\n createdBy: TombstoneCreatedBy;\n sourceMemoryId: string;\n rawContent: string;\n contentHash?: string;\n entityRef?: string;\n supersessionKey?: string;\n createdAt: string;\n}> {\n const keys =\n memory.entityRef && memory.structuredAttributes\n ? opts.supersessionKeysForFact({\n entityRef: memory.entityRef,\n structuredAttributes: memory.structuredAttributes,\n })\n : [];\n const keysToEmit = keys.length > 0 ? keys : [undefined];\n return keysToEmit.map((key) => ({\n reason: opts.reason,\n createdBy: opts.createdBy,\n sourceMemoryId: memory.id,\n rawContent: memory.content,\n ...(memory.contentHash ? { contentHash: memory.contentHash } : {}),\n ...(memory.entityRef ? { entityRef: memory.entityRef } : {}),\n ...(key ? { supersessionKey: key } : {}),\n createdAt: opts.createdAt,\n }));\n}\n"],"mappings":";;;;;;AA0KA,IAAM,mBAAmB;AAEzB,SAAS,iBAAyB;AAChC,SAAO,GAAG,gBAAgB,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AACjG;AASA,SAAS,aAAa,WAAmB,WAAmB,iBAAiC;AAC3F,SAAO,GAAG,SAAS,KAAK,SAAS,KAAK,eAAe;AACvD;AAOO,SAAS,mBAAmB,MAAqC;AACtE,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,WAAW,EAAG,QAAO;AAC1D,MAAI,EAAE,SAAS,eAAe,EAAE,SAAS,aAAc,QAAO;AAC9D,MACE,EAAE,WAAW,gBACb,EAAE,WAAW,kBACb,EAAE,WAAW,gBACb,EAAE,WAAW,4BACb;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,EAAE,mBAAmB,SAAU,QAAO;AACjD,MAAI,OAAO,EAAE,gBAAgB,SAAU,QAAO;AAC9C,MAAI,OAAO,EAAE,mBAAmB,SAAU,QAAO;AACjD,MAAI,OAAO,EAAE,cAAc,SAAU,QAAO;AAC5C,MAAI,OAAO,EAAE,cAAc,SAAU,QAAO;AAC5C,MACE,EAAE,cAAc,qBAChB,EAAE,cAAc,8BAChB,EAAE,cAAc,kBAChB,EAAE,cAAc,QAChB;AACA,WAAO;AAAA,EACT;AACA,QAAM,MAAsB;AAAA,IAC1B,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,gBAAgB,EAAE;AAAA,IAClB,aAAa,EAAE;AAAA,IACf,gBAAgB,EAAE;AAAA,IAClB,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf;AACA,MAAI,OAAO,EAAE,iBAAiB,SAAU,KAAI,eAAe,EAAE;AAC7D,MAAI,OAAO,EAAE,cAAc,SAAU,KAAI,YAAY,EAAE;AACvD,MAAI,OAAO,EAAE,oBAAoB,SAAU,KAAI,kBAAkB,EAAE;AACnE,MAAI,OAAO,EAAE,YAAY,SAAU,KAAI,UAAU,EAAE;AACnD,SAAO;AACT;AASO,IAAM,iBAAN,MAAqB;AAAA,EAwB1B,YACmB,UACA,WACA,SACA,IACjB;AAJiB;AACA;AACA;AACA;AAEjB,SAAK,cAAc,KAAK,QAAQ,eAAe;AAC/C,SAAK,gBAAgB,KAAK,QAAQ,iBAAiB;AACnD,SAAK,kBACH,KAAK,QAAQ,mBAAmB,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,cAAc,CAAC,CAAC;AAAA,EAClF;AAAA,EATmB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EA3BX,UAA4B,CAAC;AAAA;AAAA,EAEpB,SAAS,oBAAI,IAAoB;AAAA;AAAA,EAEjC,eAAe,oBAAI,IAAoB;AAAA;AAAA,EAEvC,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EAEhC,aAAa,oBAAI,IAAY;AAAA;AAAA,EAE7B,OAAO,oBAAI,IAA4B;AAAA,EAChD,SAAS;AAAA,EACT,cAAoC;AAAA,EACpC,iBAAiB;AAAA,EACjB,eAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO9B,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBL;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,IAAY,WAAmB;AAC7B,WAAO,KAAK,SAAS,QAAQ,aAAa,EAAE,IAAI;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,YAAa,QAAO,KAAK;AAClC,SAAK,cAAc,KAAK,aAAa;AACrC,QAAI;AACF,YAAM,KAAK;AAAA,IACb,UAAE;AACA,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,MAAc,eAA8B;AAK1C,SAAK,gBAAgB;AACrB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ;AAAA,IACxC,SAAS,KAAK;AAIZ,YAAM,OAAQ,KAA+B;AAC7C,UAAI,SAAS,UAAU;AAAA,MAGvB;AACA,WAAK,SAAS;AACd;AAAA,IACF;AACA,SAAK,WAAW;AAChB,QAAI,YAAY;AAChB,eAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,YAAM,QAAQ,mBAAmB,IAAI;AACrC,UAAI,CAAC,OAAO;AACV,YAAI,KAAK,KAAK,EAAE,SAAS,EAAG,cAAa;AACzC;AAAA,MACF;AACA,WAAK,WAAW,KAAK;AAAA,IACvB;AACA,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,aAAmB;AACzB,SAAK,UAAU,CAAC;AAChB,SAAK,OAAO,MAAM;AAClB,SAAK,aAAa,MAAM;AACxB,SAAK,MAAM,MAAM;AACjB,SAAK,WAAW,MAAM;AACtB,SAAK,KAAK,MAAM;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAW,OAA6B;AAC9C,SAAK,QAAQ,KAAK,KAAK;AACvB,SAAK,KAAK,IAAI,MAAM,IAAI,KAAK;AAC7B,QAAI,MAAM,aAAa,KAAK,gBAAgB,KAAK;AAC/C,WAAK,eAAe,MAAM;AAAA,IAC5B;AACA,QAAI,MAAM,SAAS,cAAc;AAC/B,UAAI,MAAM,QAAS,MAAK,WAAW,IAAI,MAAM,OAAO;AACpD;AAAA,IACF;AAOA,UAAM,KAAK,MAAM;AACjB,QAAI,MAAM,YAAa,MAAK,OAAO,IAAI,GAAG,EAAE,KAAK,MAAM,WAAW,IAAI,MAAM,EAAE;AAC9E,QAAI,MAAM,eAAgB,MAAK,aAAa,IAAI,GAAG,EAAE,KAAK,MAAM,cAAc,IAAI,MAAM,EAAE;AAC1F,QAAI,MAAM,aAAa,MAAM,iBAAiB;AAC5C,WAAK,MAAM,IAAI,aAAa,IAAI,MAAM,WAAW,MAAM,eAAe,GAAG,MAAM,EAAE;AAAA,IACnF;AAAA,EACF;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,yBAAwC;AAC5C,QAAI,CAAC,KAAK,GAAG,KAAM;AACnB,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,EAAE,OAAO;AAAA,IAC1D,QAAQ;AACN;AAAA,IACF;AACA,QAAI,YAAY,KAAK,eAAe,KAAK,OAAQ;AACjD,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,UAAM,KAAK,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cAAoB;AAC1B,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,kBAAwB;AAC9B,QAAI,CAAC,KAAK,GAAG,KAAM;AACnB,QAAI;AACF,WAAK,cAAc,KAAK,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,EAAE,OAAO;AAAA,IACnE,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,gBAAgB,OAiBF;AAClB,UAAM,KAAK,KAAK;AAChB,UAAM,YAAY,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC5D,UAAM,KAAK,eAAe;AAC1B,UAAM,QAAwB;AAAA,MAC5B;AAAA,MACA,MAAM;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,gBAAgB,MAAM;AAAA,MACtB,aAAa,MAAM,eAAe,KAAK,QAAQ,YAAY,MAAM,UAAU;AAAA,MAC3E,gBAAgB,KAAK,QAAQ,cAAc,MAAM,UAAU;AAAA,MAC3D,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,MACxD,GAAI,MAAM,kBAAkB,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,MAC1E,GAAI,MAAM,eAAe,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;AAAA,MACjE,WAAW,KAAK;AAAA,MAChB;AAAA,MACA,WAAW,MAAM;AAAA,IACnB;AACA,UAAM,KAAK,gBAAgB,KAAK;AAChC,SAAK,WAAW,KAAK;AACrB,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,aAAqB,WAAgD;AAChF,UAAM,KAAK,KAAK;AAChB,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,KAAK,eAAe;AAC1B,UAAM,QAAwB;AAAA,MAC5B;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,SAAS;AAAA,IACX;AACA,UAAM,KAAK,gBAAgB,KAAK;AAChC,SAAK,WAAW,KAAK;AACrB,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,OAAsC;AAC5D,UAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AAOrC,WAAO;AAAA,MAAmB,aAAa,KAAK,QAAQ;AAAA,MAAI,MACtD,KAAK,cAAc,YAAY;AAC7B,cAAM,KAAK,uBAAuB;AAClC,cAAM,KAAK,GAAG,OAAO,KAAK,UAAU,IAAI;AAAA,MAC1C,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,cAAiB,MAAoC;AAC3D,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,QACE,SAAS,KAAK;AAAA,QACd,WAAW,KAAK;AAAA,QAChB,aAAa,KAAK;AAAA,MACpB;AAAA,MACA,OAAO,aAAa;AAClB,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,OAAoD;AAMzD,QAAI,CAAC,KAAK,QAAQ,QAAS,QAAO;AAClC,QAAI,CAAC,KAAK,OAAQ,QAAO;AAMzB,UAAM,KAAK,MAAM;AACjB,QAAI,MAAM,aAAa;AACrB,YAAM,KAAK,KAAK,OAAO,IAAI,GAAG,EAAE,KAAK,MAAM,WAAW,EAAE;AACxD,UAAI,MAAM,CAAC,KAAK,WAAW,IAAI,EAAE,GAAG;AAClC,cAAM,QAAQ,KAAK,KAAK,IAAI,EAAE;AAC9B,YAAI,SAAS,MAAM,cAAc,IAAI;AACnC,iBAAO,EAAE,aAAa,IAAI,aAAa,SAAS,QAAQ,MAAM,OAAO;AAAA,QACvE;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,gBAAgB;AACxB,YAAM,KAAK,KAAK,aAAa,IAAI,GAAG,EAAE,KAAK,MAAM,cAAc,EAAE;AACjE,UAAI,MAAM,CAAC,KAAK,WAAW,IAAI,EAAE,GAAG;AAClC,cAAM,QAAQ,KAAK,KAAK,IAAI,EAAE;AAC9B,YAAI,SAAS,MAAM,cAAc,IAAI;AACnC,iBAAO,EAAE,aAAa,IAAI,aAAa,cAAc,QAAQ,MAAM,OAAO;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAQA,QAAI,MAAM,WAAW;AACnB,YAAM,cAAwB,CAAC;AAC/B,UAAI,MAAM,gBAAiB,aAAY,KAAK,MAAM,eAAe;AACjE,UAAI,MAAM,kBAAkB;AAC1B,mBAAW,KAAK,MAAM,kBAAkB;AACtC,cAAI,CAAC,YAAY,SAAS,CAAC,EAAG,aAAY,KAAK,CAAC;AAAA,QAClD;AAAA,MACF;AACA,iBAAW,OAAO,aAAa;AAC7B,cAAM,KAAK,KAAK,MAAM,IAAI,aAAa,IAAI,MAAM,WAAW,GAAG,CAAC;AAChE,YAAI,MAAM,CAAC,KAAK,WAAW,IAAI,EAAE,GAAG;AAClC,gBAAM,QAAQ,KAAK,KAAK,IAAI,EAAE;AAC9B,cAAI,SAAS,MAAM,cAAc,IAAI;AACnC,mBAAO,EAAE,aAAa,IAAI,aAAa,SAAS,QAAQ,MAAM,OAAO;AAAA,UACvE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ,iBAAiB,KAAK,QAAQ,sBAAsB,MAAM,gBAAgB;AACzF,YAAM,YAAY,KAAK,QAAQ;AAC/B,UAAI,OAAsE;AAC1E,iBAAW,SAAS,KAAK,SAAS;AAChC,YAAI,MAAM,SAAS,YAAa;AAChC,YAAI,MAAM,cAAc,MAAM,UAAW;AACzC,YAAI,KAAK,WAAW,IAAI,MAAM,EAAE,EAAG;AACnC,YAAI,CAAC,MAAM,eAAgB;AAC3B,cAAM,QAAQ,KAAK,QAAQ,mBAAmB,MAAM,gBAAgB,MAAM,cAAc;AACxF,YAAI,SAAS,cAAc,CAAC,QAAQ,QAAQ,KAAK,QAAQ;AACvD,iBAAO,EAAE,IAAI,MAAM,IAAI,QAAQ,MAAM,QAAQ,MAAM;AAAA,QACrD;AAAA,MACF;AACA,UAAI,MAAM;AACR,eAAO,EAAE,aAAa,KAAK,IAAI,aAAa,YAAY,QAAQ,KAAK,OAAO;AAAA,MAC9E;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,MAAM,cAAc,OAOC;AACnB,QAAI,CAAC,KAAK,QAAQ,QAAS,QAAO;AAClC,UAAM,KAAK,uBAAuB;AAClC,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK,QAAQ;AAAA,MAClB,CAAC,UACC,MAAM,SAAS,eACf,CAAC,KAAK,WAAW,IAAI,MAAM,EAAE,KAC7B,MAAM,cAAc,KAAK,aACzB,MAAM,mBAAmB,MAAM,mBAC9B,MAAM,gBAAgB,UAAa,MAAM,gBAAgB,MAAM,iBAC/D,MAAM,cAAc,UAAa,MAAM,cAAc,MAAM,eAC3D,MAAM,oBAAoB,UAAa,MAAM,oBAAoB,MAAM,qBACvE,MAAM,cAAc,UAAa,MAAM,cAAc,MAAM,eAC3D,MAAM,iBAAiB,UAAa,MAAM,iBAAiB,MAAM;AAAA,IACtE;AAAA,EACF;AAAA;AAAA,EAIA,QAAwB;AACtB,QAAI,SAAS;AACb,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,MAAM,SAAS,eAAe,CAAC,KAAK,WAAW,IAAI,MAAM,EAAE,EAAG,WAAU;AAAA,IAC9E;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,SAAS,KAAK,WAAW;AAAA,MACzB,cAAc,KAAK;AAAA,MACnB,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,WAAsC;AACpC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QAAQ,iBAAsE;AAUlF,UAAM,eAAe,MAAM;AAAA,MAAmB,aAAa,KAAK,QAAQ;AAAA,MAAI,MAC1E,KAAK,cAAc,YAAY;AAC7B,cAAM,KAAK,uBAAuB;AAGlC,cAAM,sBAAsB,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY;AAM9E,cAAM,oBAAoB,KAAK,QAAQ;AAAA,UACrC,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,cAAc,KAAK;AAAA,QACxD;AAMA,cAAM,mBAAmB,oBAAI,IAAoB;AACjD,mBAAW,KAAK,KAAK,SAAS;AAC5B,cAAI,EAAE,SAAS,aAAa;AAC1B,6BAAiB,IAAI,GAAG,EAAE,cAAc,KAAW,EAAE,mBAAmB,EAAE,IAAI,EAAE,EAAE;AAAA,UACpF;AAAA,QACF;AACA,cAAM,UAA4B,gBAAgB,IAAI,CAAC,OAAO;AAAA,UAC5D,IACE,iBAAiB,IAAI,GAAG,EAAE,QAAQ,KAAW,EAAE,mBAAmB,EAAE,EAAE,KACtE,eAAe;AAAA,UACjB,MAAM;AAAA,UACN,QAAQ,EAAE;AAAA,UACV,gBAAgB,EAAE;AAAA,UAClB,aAAa,EAAE,eAAe,KAAK,QAAQ,YAAY,EAAE,UAAU;AAAA,UACnE,gBAAgB,KAAK,QAAQ,cAAc,EAAE,UAAU;AAAA,UACvD,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,UAChD,GAAI,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,IAAI,CAAC;AAAA,UAClE,WAAW,KAAK;AAAA,UAChB,WAAW,EAAE;AAAA,UACb,WAAW,EAAE;AAAA,QACf,EAAE;AAEF,gBAAQ;AAAA,UAAK,CAAC,GAAG,MACf,EAAE,cAAc,EAAE,YACd,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,IACrC,EAAE,YAAY,EAAE,YAAY,KAAK;AAAA,QACvC;AACA,cAAM,MAAM,CAAC,GAAG,SAAS,GAAG,qBAAqB,GAAG,iBAAiB,EAAE;AAAA,UAAK,CAAC,GAAG,MAC9E,EAAE,cAAc,EAAE,YACd,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,IACrC,EAAE,YAAY,EAAE,YAAY,KAAK;AAAA,QACvC;AACA,cAAM,aACJ,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,IAAI,SAAS,IAAI,OAAO;AAC1E,cAAM,KAAK,GAAG,MAAM,KAAK,UAAU,UAAU;AAI7C,aAAK,WAAW;AAChB,mBAAW,SAAS,IAAK,MAAK,WAAW,KAAK;AAC9C,aAAK,iBAAiB;AACtB,aAAK,SAAS;AACd,aAAK,YAAY;AACjB,eAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AACF;AAuBO,SAAS,iCACd,UAaA,MASuB;AACvB,QAAM,UAAiC,CAAC;AACxC,aAAW,KAAK,UAAU;AACxB,UAAM,SAAS,EAAE,YAAY;AAC7B,QAAI,WAAW,gBAAgB,WAAW,cAAc,WAAW,YAAa;AAChF,QAAI,EAAE,YAAY,aAAa,OAAQ;AACvC,UAAM,SACJ,WAAW,eAAe,iBAAiB,WAAW,cAAc,eAAe;AACrF,UAAM,YACJ,WAAW,iBAAiB,iBAAiB;AAC/C,UAAM,YAAY,EAAE,YAAY;AAChC,UAAM,OACJ,aAAa,EAAE,YAAY,uBACvB,KAAK,wBAAwB;AAAA,MAC3B;AAAA,MACA,sBAAsB,EAAE,YAAY;AAAA,IACtC,CAAC,IACD,CAAC;AAMP,UAAM,aAAa,KAAK,SAAS,IAAI,OAAO,CAAC,MAAS;AACtD,eAAW,OAAO,YAAY;AAC5B,cAAQ,KAAK;AAAA,QACX,UAAU,EAAE,YAAY;AAAA,QACxB,YAAY,KAAK,cAAc,EAAE,OAAO;AAAA,QACxC,aAAa,EAAE,YAAY;AAAA,QAC3B,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,QACjC,GAAI,MAAM,EAAE,iBAAiB,IAAI,IAAI,CAAC;AAAA,QACtC;AAAA,QACA;AAAA,QACA,WAAW,EAAE,YAAY,WAAW,EAAE,YAAY,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAUO,SAAS,+BACd,OACA,IAQA,OAKuB;AACvB,MAAI,GAAG,WAAW,oBAAoB,GAAG,UAAW,QAAO;AAC3D,MAAI,EAAE,GAAG,WAAW,UAAa,GAAG,WAAW,YAAY,GAAG,WAAW,kBAAmB,QAAO;AACnG,QAAM,QAAQ,MAAM,OAAO;AAAA,IACzB,aAAa,GAAG;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,GAAI,GAAG,YAAY,EAAE,WAAW,GAAG,UAAU,IAAI,CAAC;AAAA,IAClD,GAAI,MAAM,iBAAiB,SAAS,IAAI,EAAE,kBAAkB,MAAM,iBAAiB,IAAI,CAAC;AAAA,IACxF,WAAW,MAAM;AAAA,EACnB,CAAC;AACD,MAAI,CAAC,MAAO,QAAO;AACnB,KAAG,SAAS;AACZ,KAAG,YAAY,MAAM;AACrB,KAAG,qBAAqB,MAAM;AAC9B,SAAO;AACT;AAaO,SAAS,gCACd,QAOA,MAkBC;AACD,QAAM,OACJ,OAAO,aAAa,OAAO,uBACvB,KAAK,wBAAwB;AAAA,IAC3B,WAAW,OAAO;AAAA,IAClB,sBAAsB,OAAO;AAAA,EAC/B,CAAC,IACD,CAAC;AACP,QAAM,aAAa,KAAK,SAAS,IAAI,OAAO,CAAC,MAAS;AACtD,SAAO,WAAW,IAAI,CAAC,SAAS;AAAA,IAC9B,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,gBAAgB,OAAO;AAAA,IACvB,YAAY,OAAO;AAAA,IACnB,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;AAAA,IAChE,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;AAAA,IAC1D,GAAI,MAAM,EAAE,iBAAiB,IAAI,IAAI,CAAC;AAAA,IACtC,WAAW,KAAK;AAAA,EAClB,EAAE;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/wearables/corrections.ts","../src/wearables/redaction.ts"],"sourcesContent":["/**\n * Wearable transcript corrections — user-specific replacement rules.\n *\n * ASR engines consistently mishear the same proper nouns for the same\n * person (\"remnick\" for \"Remnic\", a colleague's name, product jargon).\n * Rules come from two places, merged at sync time:\n *\n * 1. `wearables.corrections` in plugin config (declarative, versioned\n * with the operator's config).\n * 2. A CLI-managed rules file at `state/wearables/corrections.json`\n * (added interactively via `remnic wearables corrections add`).\n *\n * Literal rules are regex-escaped before compilation and replacements\n * are applied via a function (never a replacement string) so `$` in\n * either side can't corrupt output.\n */\n\nimport { promises as fsPromises } from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport type { WearableCorrectionRule } from \"./types.js\";\n\nexport interface CompiledCorrectionRule {\n rule: WearableCorrectionRule;\n pattern: RegExp;\n}\n\nexport interface CorrectionApplication {\n text: string;\n applied: number;\n}\n\n/** Hard cap on rule pattern length (bounds hostile/pathological regexes). */\nconst MAX_PATTERN_LENGTH = 256;\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Validate and compile a correction rule. Throws a descriptive error on\n * invalid input (empty match, regex that doesn't compile, regex that\n * matches the empty string) — callers surface this at config parse or\n * CLI time rather than skipping the rule silently.\n */\nexport function compileCorrectionRule(\n rule: WearableCorrectionRule,\n label: string,\n): CompiledCorrectionRule {\n if (typeof rule.match !== \"string\" || rule.match.length === 0) {\n throw new Error(`${label}: match must be a non-empty string`);\n }\n if (rule.match.length > MAX_PATTERN_LENGTH) {\n throw new Error(\n `${label}: match exceeds ${MAX_PATTERN_LENGTH} characters — correction patterns must stay short`,\n );\n }\n if (typeof rule.replace !== \"string\") {\n throw new Error(`${label}: replace must be a string`);\n }\n const flags = rule.caseInsensitive === false ? \"g\" : \"gi\";\n let pattern: RegExp;\n if (rule.regex === true) {\n try {\n // Operator-supplied regexes are the documented feature here\n // (rules live in the operator's own config / state file, never in\n // request input); the length cap above bounds pathological\n // patterns. CodeQL js/regex-injection is dismissed by design for\n // this site.\n pattern = new RegExp(rule.match, flags);\n } catch (err) {\n throw new Error(\n `${label}: match is not a valid regular expression: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n } else {\n // Literal rules match on word boundaries when both edges of the\n // match are word characters, so \"remnick\" doesn't fire inside\n // \"remnickson\" unless the user opts into regex mode.\n const escaped = escapeRegExp(rule.match);\n const leading = /^[\\p{L}\\p{N}_]/u.test(rule.match) ? \"\\\\b\" : \"\";\n const trailing = /[\\p{L}\\p{N}_]$/u.test(rule.match) ? \"\\\\b\" : \"\";\n pattern = new RegExp(`${leading}${escaped}${trailing}`, flags);\n }\n if (pattern.test(\"\")) {\n throw new Error(\n `${label}: pattern matches the empty string and would corrupt every transcript`,\n );\n }\n pattern.lastIndex = 0;\n return { rule, pattern };\n}\n\n/** Compile a rule list, labeling errors with their index. */\nexport function compileCorrectionRules(\n rules: WearableCorrectionRule[],\n labelPrefix: string,\n): CompiledCorrectionRule[] {\n return rules.map((rule, index) =>\n compileCorrectionRule(rule, `${labelPrefix}[${index}]`),\n );\n}\n\n/** Apply every applicable rule to a piece of transcript text. */\nexport function applyCorrections(\n text: string,\n rules: CompiledCorrectionRule[],\n sourceId: string,\n): CorrectionApplication {\n let applied = 0;\n let result = text;\n for (const { rule, pattern } of rules) {\n if (\n Array.isArray(rule.sources) &&\n rule.sources.length > 0 &&\n !rule.sources.includes(sourceId)\n ) {\n continue;\n }\n pattern.lastIndex = 0;\n result = result.replace(pattern, () => {\n applied += 1;\n // Replacement via function: `$` in rule.replace stays literal.\n return rule.replace;\n });\n }\n return { text: result, applied };\n}\n\n// ---------------------------------------------------------------------------\n// CLI-managed rules file\n// ---------------------------------------------------------------------------\n\ninterface CorrectionsFileShape {\n version: 1;\n rules: WearableCorrectionRule[];\n}\n\nexport function correctionsFilePath(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"wearables\", \"corrections.json\");\n}\n\n/**\n * Load CLI-managed correction rules. A missing file means no rules; a\n * malformed file throws (operators should know their corrections are\n * not being applied rather than silently losing them).\n */\nexport async function loadCorrectionsFile(\n memoryDir: string,\n): Promise<WearableCorrectionRule[]> {\n const filePath = correctionsFilePath(memoryDir);\n let raw: string;\n try {\n raw = await fsPromises.readFile(filePath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `wearables corrections file is not valid JSON (state/wearables/corrections.json): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n Array.isArray(parsed) ||\n !Array.isArray((parsed as CorrectionsFileShape).rules)\n ) {\n throw new Error(\n 'wearables corrections file has an unexpected shape (state/wearables/corrections.json); expected {\"version\":1,\"rules\":[...]}',\n );\n }\n const rules = (parsed as CorrectionsFileShape).rules;\n // Validate every persisted rule up front so a hand-edited bad rule\n // fails at load with its index, not mid-sync.\n compileCorrectionRules(rules, \"state corrections\");\n return rules;\n}\n\n/** Persist CLI-managed rules atomically (temp file + rename). */\nexport async function saveCorrectionsFile(\n memoryDir: string,\n rules: WearableCorrectionRule[],\n): Promise<void> {\n compileCorrectionRules(rules, \"state corrections\");\n const filePath = correctionsFilePath(memoryDir);\n await fsPromises.mkdir(path.dirname(filePath), { recursive: true });\n const payload: CorrectionsFileShape = { version: 1, rules };\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now().toString(36)}`;\n await fsPromises.writeFile(\n tmpPath,\n `${JSON.stringify(payload, null, 2)}\\n`,\n \"utf-8\",\n );\n try {\n await fsPromises.rename(tmpPath, filePath);\n } catch (err) {\n // Clean up the temp file on rename failure; the original (if any)\n // is untouched.\n await fsPromises.unlink(tmpPath).catch(() => undefined);\n throw err;\n }\n}\n","/**\n * Wearable transcript redaction — privacy guard applied before any\n * transcript text is persisted or fed to extraction.\n *\n * Always-on recorders capture things nobody intended to store: card\n * numbers read aloud, SSNs dictated to a pharmacy line. Built-in\n * patterns cover the unambiguous, high-sensitivity cases; users can add\n * their own regexes via `wearables.redactionPatterns` (validated at\n * config parse — invalid patterns are rejected loudly, never ignored).\n *\n * All built-in patterns are simple linear scans (no nested quantifiers)\n * to stay safely outside polynomial-ReDoS territory.\n */\n\nimport type { WearableConversation } from \"./types.js\";\n\nexport const REDACTION_PLACEHOLDER = \"[redacted]\";\n\n/**\n * Built-in patterns. Conservative by design — false positives erase\n * real transcript content, so each pattern targets formats that are\n * near-certain PII:\n * - US SSN with separators (123-45-6789). Bare 9-digit runs are NOT\n * matched (too many false positives: ids, tracking numbers).\n * - Payment-card-like runs: 13–19 digits in groups separated by\n * spaces/dashes (4111 1111 1111 1111) or contiguous 15–16 digits.\n */\nconst BUILT_IN_PATTERNS: RegExp[] = [\n /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n // Starts and ENDS on a digit so a trailing separator is never\n // consumed (replacing it would glue the placeholder to the next word).\n /\\b\\d(?:[ -]?\\d){12,18}\\b/g,\n];\n\n/** Minimum digit count before a digit-run is treated as a card number. */\nconst CARD_MIN_DIGITS = 13;\n\nexport interface RedactionResult {\n text: string;\n redactions: number;\n}\n\nexport function redactText(\n text: string,\n userPatterns: RegExp[],\n): RedactionResult {\n let redactions = 0;\n let result = text;\n\n // SSN pattern first (more specific than the digit-run pattern).\n result = result.replace(BUILT_IN_PATTERNS[0], () => {\n redactions += 1;\n return REDACTION_PLACEHOLDER;\n });\n\n // Digit-run pattern with a post-match digit-count check so short\n // grouped numbers (\"call 555 0125 today\") survive.\n result = result.replace(BUILT_IN_PATTERNS[1], (match) => {\n const digits = match.replace(/\\D/g, \"\");\n if (digits.length < CARD_MIN_DIGITS || digits.length > 19) {\n return match;\n }\n redactions += 1;\n return REDACTION_PLACEHOLDER;\n });\n\n for (const pattern of userPatterns) {\n result = result.replace(pattern, () => {\n redactions += 1;\n return REDACTION_PLACEHOLDER;\n });\n }\n\n return { text: result, redactions };\n}\n\n/**\n * Compile user-supplied redaction patterns. Throws with a descriptive\n * message on the first invalid pattern — config parsing surfaces this\n * to the operator instead of silently skipping the rule.\n */\nexport function compileRedactionPatterns(patterns: string[]): RegExp[] {\n return patterns.map((pattern, index) => {\n if (typeof pattern !== \"string\" || pattern.trim().length === 0) {\n throw new Error(\n `wearables.redactionPatterns[${index}] must be a non-empty string`,\n );\n }\n if (pattern.length > 256) {\n throw new Error(\n `wearables.redactionPatterns[${index}] exceeds 256 characters — redaction patterns must stay short`,\n );\n }\n try {\n // Operator-supplied regexes from the operator's own config —\n // length-capped above; never request input.\n return new RegExp(pattern, \"gi\");\n } catch (err) {\n throw new Error(\n `wearables.redactionPatterns[${index}] is not a valid regular expression: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n });\n}\n\nconst OFF_THE_RECORD = /\\boff\\s+the\\s+record\\b/i;\nconst BACK_ON_THE_RECORD = /\\b(?:back\\s+)?on\\s+the\\s+record\\b/i;\n\nexport interface OffTheRecordResult {\n conversation: WearableConversation;\n droppedSegments: number;\n}\n\n/**\n * Drop segments between a spoken \"off the record\" marker and the next\n * \"(back) on the record\" marker (or conversation end). The marker\n * segments themselves are kept, with the off-record span replaced by a\n * visible placeholder so the transcript shows that content was elided\n * by request rather than lost.\n */\nexport function applyOffTheRecord(\n conversation: WearableConversation,\n): OffTheRecordResult {\n let offRecord = false;\n let droppedSegments = 0;\n const segments = [];\n for (const segment of conversation.segments) {\n if (!offRecord && OFF_THE_RECORD.test(segment.text)) {\n offRecord = true;\n segments.push({\n ...segment,\n text: \"[off the record — segment elided]\",\n });\n continue;\n }\n if (offRecord) {\n if (BACK_ON_THE_RECORD.test(segment.text)) {\n offRecord = false;\n segments.push({\n ...segment,\n text: \"[back on the record]\",\n });\n } else {\n droppedSegments += 1;\n }\n continue;\n }\n segments.push(segment);\n }\n return {\n conversation: { ...conversation, segments },\n droppedSegments,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/structured-attributes.ts"],"sourcesContent":["/**\n * Shared helpers for the `\\n[Attributes: …]` enrichment suffix that\n * `writeMemory` appends to a stored body when `structuredAttributes` are\n * present.\n *\n * Extracted here (out of `storage.ts`) so every reader — the storage\n * manager, the wearable service, and the coding surfaces — strips the suffix\n * with ONE definition. Previously `coding/architecture-surfaces.ts` carried a\n * weaker local copy that only checked the trailing `]`, which could truncate\n * card markdown that legitimately ends in a `[Attributes: …]` line (cursor\n * review: duplicate attribute suffix stripper).\n *\n * String operations, not regex — CodeQL `js/polynomial-redos` flags\n * suffix-anchored patterns over library-supplied content.\n */\n\nimport { sanitizeMemoryContent } from \"./sanitize.js\";\n\n/**\n * Remove the `[Attributes: …]` suffix `writeMemory` appends to the stored\n * body when structuredAttributes are present, yielding the raw fact/card text\n * for content comparison or client display. Inverse companion of\n * `normalizeAttributePairs` enrichment in `storage.ts`.\n *\n * Returns the stripped (trimmed) body when the marker opens the final\n * non-empty line and its inner payload contains no premature `]` or newline\n * (mirrors the exact shape `normalizeAttributePairs` emits). Otherwise\n * returns the trimmed input unchanged — never truncates legitimate content.\n */\nexport function stripAttributesSuffix(content: string): string {\n const trimmed = content.trimEnd();\n if (!trimmed.endsWith(\"]\")) return content.trim();\n const marker = \"\\n[Attributes: \";\n const markerIndex = trimmed.lastIndexOf(marker);\n if (markerIndex === -1) return content.trim();\n // The block must be the FINAL line and contain no \"]\" before the\n // closing bracket (mirrors the shape normalizeAttributePairs emits).\n const inner = trimmed.slice(markerIndex + marker.length, -1);\n if (inner.includes(\"]\") || inner.includes(\"\\n\")) return content.trim();\n return trimmed.slice(0, markerIndex).trim();\n}\n// ---------------------------------------------------------------------------\n// Persisted-body assembly (issue #1989 PR2)\n// ---------------------------------------------------------------------------\n//\n// `writeMemory` historically enriched content with the attribute suffix and\n// then sanitized the COMBINED body inline. That assembly is the persisted\n// form both the file body and write-idempotency fingerprints must agree on\n// (AGENTS.md §13), so it lives here — shared by `StorageManager.writeMemory`\n// and `composeMemoryEnvelope` — instead of existing twice.\n\n/**\n * Canonical `key: value; …` rendering of structured attributes. Keys are\n * trimmed + lowercased, values trimmed, pairs sorted by normalized key.\n * Moved verbatim from `storage.ts` (which re-exports it for API stability);\n * used at the write path, the dedup-lookup path, and envelope assembly.\n */\nexport function normalizeAttributePairs(pairs: Record<string, string>): string {\n return Object.entries(pairs)\n .map(([k, v]) => [k.trim().toLowerCase(), v.trim()] as [string, string])\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \");\n}\n\nexport interface AssembledPersistBody {\n /** The exact body persistence writes: enriched then sanitized. */\n text: string;\n /** False when sanitization redacted injection-bearing text. */\n clean: boolean;\n /** Matched injection pattern sources (for the storage warn log). */\n violations: string[];\n}\n\n/**\n * Assemble the persisted body exactly as `writeMemory` does: append the\n * normalized attribute suffix when attributes are present, then sanitize the\n * combined result. ONE definition — byte-identity between the legacy write\n * path and the sealed-envelope path is structural, not tested-by-hope.\n */\nexport function assemblePersistedBody(\n content: string,\n structuredAttributes?: Record<string, string>,\n): AssembledPersistBody {\n let enriched = content;\n if (structuredAttributes && Object.keys(structuredAttributes).length > 0) {\n enriched = `${content}\\n[Attributes: ${normalizeAttributePairs(structuredAttributes)}]`;\n }\n const sanitized = sanitizeMemoryContent(enriched);\n return { text: sanitized.text, clean: sanitized.clean, violations: sanitized.violations };\n}\n"],"mappings":";;;;;AA6BO,SAAS,sBAAsB,SAAyB;AAC7D,QAAM,UAAU,QAAQ,QAAQ;AAChC,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO,QAAQ,KAAK;AAChD,QAAM,SAAS;AACf,QAAM,cAAc,QAAQ,YAAY,MAAM;AAC9C,MAAI,gBAAgB,GAAI,QAAO,QAAQ,KAAK;AAG5C,QAAM,QAAQ,QAAQ,MAAM,cAAc,OAAO,QAAQ,EAAE;AAC3D,MAAI,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,EAAG,QAAO,QAAQ,KAAK;AACrE,SAAO,QAAQ,MAAM,GAAG,WAAW,EAAE,KAAK;AAC5C;AAiBO,SAAS,wBAAwB,OAAuC;AAC7E,SAAO,OAAO,QAAQ,KAAK,EACxB,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,YAAY,GAAG,EAAE,KAAK,CAAC,CAAqB,EACtE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EACrC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,EAC5B,KAAK,IAAI;AACd;AAiBO,SAAS,sBACd,SACA,sBACsB;AACtB,MAAI,WAAW;AACf,MAAI,wBAAwB,OAAO,KAAK,oBAAoB,EAAE,SAAS,GAAG;AACxE,eAAW,GAAG,OAAO;AAAA,eAAkB,wBAAwB,oBAAoB,CAAC;AAAA,EACtF;AACA,QAAM,YAAY,sBAAsB,QAAQ;AAChD,SAAO,EAAE,MAAM,UAAU,MAAM,OAAO,UAAU,OAAO,YAAY,UAAU,WAAW;AAC1F;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport type { MemoryFrontmatter, MemoryStatus } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport {\n type ReconcileFileState,\n type ReconcilePlan,\n type ReconcilePlanEntry,\n type ReconcileSemanticAgreement,\n type ReconcileSemanticChange,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n}\n\ntype ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };\n\nexport interface ReconcileManifest {\n format: typeof RECONCILE_MANIFEST_FORMAT;\n schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;\n files: ReconcileManifestFile[];\n}\n\nexport type ReconcileMemoryParser = (\n raw: string,\n) => { frontmatter: MemoryFrontmatter; content: string } | null;\n\nexport interface BuildReconcileManifestOptions {\n files: Iterable<ReconcileFileState>;\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;\n parseMemory: ReconcileMemoryParser;\n cachedFiles?: Iterable<ReconcileManifestFile>;\n}\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/i;\nconst MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);\n\nfunction isMemoryPath(filePath: string): boolean {\n if (!filePath.endsWith(\".md\")) return false;\n const segments = filePath.split(\"/\");\n let index = 0;\n if (segments[index] === \"cold\" || segments[index] === \"archive\") index += 1;\n return MEMORY_DIRS.has(segments[index] ?? \"\");\n}\n\n\nfunction parsedMemoryIdentity(\n filePath: string,\n raw: Buffer | string,\n parseMemory: ReconcileMemoryParser,\n): ReconcileMemoryIdentity | undefined {\n if (!isMemoryPath(filePath)) return undefined;\n const parsed = parseMemory(Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : raw);\n if (!parsed?.frontmatter.id) return undefined;\n const storedHash = parsed.frontmatter.contentHash;\n const contentHash =\n storedHash && SHA256_PATTERN.test(storedHash)\n ? storedHash.toLowerCase()\n : ContentHashIndex.computeHash(parsed.content);\n return {\n id: parsed.frontmatter.id,\n category: parsed.frontmatter.category,\n contentHash,\n status: inferMemoryStatus(parsed.frontmatter, filePath),\n };\n}\n\nexport async function buildReconcileManifestFile(\n file: ReconcileFileState,\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>,\n parseMemory: ReconcileMemoryParser,\n): Promise<ReconcileManifestFile> {\n let raw: Buffer | string | null = null;\n if (isMemoryPath(file.path)) {\n try {\n raw = await readFile(file);\n } catch {\n raw = null;\n }\n }\n if (raw !== null && createHash(\"sha256\").update(raw).digest(\"hex\") !== file.sha256.toLowerCase()) {\n raw = null;\n }\n const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw, parseMemory);\n return { ...file, ...(memory ? { memory } : {}) };\n}\n\nexport async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {\n const cachedByPath = new Map<string, ReconcileManifestFile>();\n for (const cached of options.cachedFiles ?? []) {\n cachedByPath.set(cached.path, cached);\n }\n\n const files: ReconcileManifestFile[] = [];\n for (const file of options.files) {\n const cached = cachedByPath.get(file.path);\n if (cached?.sha256.toLowerCase() === file.sha256.toLowerCase()) {\n files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });\n continue;\n }\n\n files.push(await buildReconcileManifestFile(file, options.readFile, options.parseMemory));\n }\n files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));\n return {\n format: RECONCILE_MANIFEST_FORMAT,\n schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,\n files,\n };\n}\n\nfunction activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {\n const result = new Map<string, ActiveFactManifestFile>();\n for (const file of manifest?.files ?? []) {\n if (file.memory?.category === \"fact\" && file.memory.status === \"active\") {\n result.set(file.path, file as ActiveFactManifestFile);\n }\n }\n return result;\n}\n\nfunction contentHashRows(files: Iterable<ActiveFactManifestFile>): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash: file.memory.contentHash });\n }\n return rows;\n}\n\nfunction comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {\n if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nfunction semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nfunction classifySemanticChange(\n current: ReconcileSemanticAgreement,\n prior: ReconcileSemanticAgreement | undefined\n): ReconcileSemanticChange {\n if (!prior) return \"unchanged\";\n const localChanged = current.local.sha256 !== prior.local.sha256;\n const peerChanged = current.peer.sha256 !== prior.peer.sha256;\n if (localChanged && peerChanged) return \"both_modified\";\n if (localChanged) return \"local_changed\";\n if (peerChanged) return \"peer_changed\";\n return \"unchanged\";\n}\n\nexport function collapseActiveFactDuplicates(\n plan: ReconcilePlan,\n localManifests: ReadonlyMap<string, ReconcileManifest>,\n peerManifests: ReadonlyMap<string, ReconcileManifest>,\n priorSemanticAgreements?: ReadonlyMap<string, readonly ReconcileSemanticAgreement[]>,\n): ReconcilePlan {\n const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();\n for (const entry of plan.entries) {\n const entries = entriesByNamespace.get(entry.namespace) ?? [];\n entries.push({ ...entry });\n entriesByNamespace.set(entry.namespace, entries);\n }\n\n let changed = false;\n for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {\n const entries = entriesByNamespace.get(namespace) ?? [];\n const localManifest = localManifests.get(namespace);\n const peerManifest = peerManifests.get(namespace);\n const localByPath = activeFactByPath(localManifest);\n const peerByPath = activeFactByPath(peerManifest);\n const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));\n const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));\n const priorSemanticByPathPair = new Map(\n (priorSemanticAgreements?.get(namespace) ?? []).map((agreement) => [\n semanticAgreementKey(agreement),\n agreement,\n ])\n );\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n for (const file of peerByPath.values()) {\n const hash = file.memory.contentHash;\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {\n const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {\n const opposite = peerFilesByPath.get(file.path);\n const activeOpposite = peerByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {\n const opposite = localFilesByPath.get(file.path);\n const activeOpposite = localByPath.get(file.path);\n return opposite === undefined || activeOpposite?.memory.contentHash === hash;\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const localFile = localByPath.get(localPath);\n const peerFile = peerByPath.get(peerPath);\n if (!localFile || !peerFile) continue;\n const duplicatePaths = new Set([\n ...localCandidates.map((file) => file.path),\n ...peerCandidates.map((file) => file.path),\n ]);\n const authoritativeSamePathEntries = new Set(entries.filter(\n (entry) =>\n duplicatePaths.has(entry.path)\n && localFilesByPath.has(entry.path)\n && peerFilesByPath.has(entry.path)\n && entry.action !== \"identical\"\n ));\n if (authoritativeSamePathEntries.size > 0) {\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path)\n && !authoritativeSamePathEntries.has(entry)\n && (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n changed = true;\n }\n }\n continue;\n }\n const unsafeEntry = entries.some(\n (entry) => duplicatePaths.has(entry.path) && (entry.action === \"suppress\" || entry.action === \"conflict\")\n );\n if (unsafeEntry) continue;\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path) &&\n (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n }\n }\n const semanticAgreement: ReconcileSemanticAgreement = {\n local: { path: localPath, sha256: localFile.sha256 },\n peer: { path: peerPath, sha256: peerFile.sha256 },\n };\n replacements.push({\n path: localPath < peerPath ? localPath : peerPath,\n namespace,\n action: \"identical\",\n reason: \"semantic_duplicate\",\n semanticAgreement,\n semanticChange: classifySemanticChange(\n semanticAgreement,\n priorSemanticByPathPair.get(semanticAgreementKey(semanticAgreement))\n ),\n });\n changed = true;\n continue;\n }\n\n const sameSideEntries = entries.filter((entry) => {\n if (localPath && entry.reason === \"local_only\") {\n return localCandidates.some((file) => file.path === entry.path);\n }\n if (peerPath && entry.reason === \"peer_only\") {\n return peerCandidates.some((file) => file.path === entry.path);\n }\n return false;\n });\n const canonicalPath = localPath ?? peerPath;\n const hasSharedCanonical = localPath !== undefined && localPath === peerPath;\n if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {\n continue;\n }\n for (const entry of sameSideEntries) {\n if (entry.path !== canonicalPath) {\n removed.add(entry);\n changed = true;\n }\n }\n }\n\n if (removed.size > 0 || replacements.length > 0) {\n entriesByNamespace.set(\n namespace,\n [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":"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","names":["entries"]}
@@ -1,14 +0,0 @@
1
- // src/content-hash.ts
2
- import { createHash } from "crypto";
3
- function normalizeContent(content) {
4
- return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
5
- }
6
- function computeContentHash(content) {
7
- return createHash("sha256").update(normalizeContent(content)).digest("hex");
8
- }
9
-
10
- export {
11
- normalizeContent,
12
- computeContentHash
13
- };
14
- //# sourceMappingURL=chunk-W5H6ZRPT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/content-hash.ts"],"sourcesContent":["/**\n * content-hash.ts — standalone content normalization + SHA-256 hashing.\n *\n * Extracted from `ContentHashIndex` (storage.ts) so correction modules and\n * other consumers can hash content WITHOUT importing the storage god file\n * (issue #1533 directStorageImports ratchet). The static methods on\n * `ContentHashIndex` delegate here to guarantee identical results.\n */\n\nimport { createHash } from \"node:crypto\";\n\n/** Normalize content: lowercase, strip non-alphanumerics, collapse whitespace. */\nexport function normalizeContent(content: string): string {\n return content\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Normalize content and compute SHA-256 hash. */\nexport function computeContentHash(content: string): string {\n return createHash(\"sha256\").update(normalizeContent(content)).digest(\"hex\");\n}\n"],"mappings":";AASA,SAAS,kBAAkB;AAGpB,SAAS,iBAAiB,SAAyB;AACxD,SAAO,QACJ,YAAY,EACZ,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAGO,SAAS,mBAAmB,SAAyB;AAC1D,SAAO,WAAW,QAAQ,EAAE,OAAO,iBAAiB,OAAO,CAAC,EAAE,OAAO,KAAK;AAC5E;","names":[]}