@remnic/core 9.69.46 → 9.69.47

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 (589) hide show
  1. package/dist/access-admin-ops-surface.d.ts +15 -14
  2. package/dist/access-admin-ops-surface.js +42 -41
  3. package/dist/access-authorization-probe.d.ts +15 -14
  4. package/dist/access-authorization-probe.js +43 -42
  5. package/dist/access-boundary.d.ts +15 -14
  6. package/dist/access-boundary.js +42 -41
  7. package/dist/access-cli.js +119 -114
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +15 -14
  11. package/dist/access-extraction-force-flush.js +42 -41
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-briefing.d.ts +15 -14
  14. package/dist/access-http-briefing.js +43 -42
  15. package/dist/access-http-lcm-compaction.d.ts +15 -14
  16. package/dist/access-http-lifecycle-flush.d.ts +15 -14
  17. package/dist/access-http-offline-stream.d.ts +15 -14
  18. package/dist/access-http-query.d.ts +2 -2
  19. package/dist/access-http-query.js +44 -43
  20. package/dist/access-http.d.ts +16 -15
  21. package/dist/access-http.js +67 -63
  22. package/dist/access-identity-continuity-surface.d.ts +11 -11
  23. package/dist/access-identity-continuity-surface.js +42 -41
  24. package/dist/access-lcm-surface.d.ts +15 -14
  25. package/dist/access-lcm-surface.js +42 -41
  26. package/dist/access-mcp-output-schemas.js +1 -1
  27. package/dist/access-mcp-shared-context-tools.d.ts +15 -14
  28. package/dist/access-mcp.d.ts +15 -14
  29. package/dist/access-mcp.js +55 -52
  30. package/dist/access-memory-search-fanout.d.ts +2 -2
  31. package/dist/{access-namespace-preflight-DDnsN22V.d.ts → access-namespace-preflight-CetF9ziA.d.ts} +1 -1
  32. package/dist/access-namespace-preflight.d.ts +3 -3
  33. package/dist/access-namespace-preflight.js +42 -41
  34. package/dist/access-observe-transcript.d.ts +11 -11
  35. package/dist/access-observe-write-category.d.ts +3 -3
  36. package/dist/access-observe-write-category.js +4 -4
  37. package/dist/access-observe-write-surface.d.ts +15 -14
  38. package/dist/access-observe-write-surface.js +42 -41
  39. package/dist/access-offline-manifest.d.ts +11 -11
  40. package/dist/access-offline-manifest.js +2 -2
  41. package/dist/access-operations-batch.js +44 -43
  42. package/dist/access-operations.d.ts +16 -15
  43. package/dist/access-operations.js +51 -49
  44. package/dist/access-recall-concurrency.d.ts +15 -14
  45. package/dist/access-recall-concurrency.js +42 -41
  46. package/dist/access-recall-response.d.ts +15 -14
  47. package/dist/access-recall-response.js +42 -41
  48. package/dist/access-recall-surface.d.ts +15 -14
  49. package/dist/access-recall-surface.js +42 -41
  50. package/dist/access-schema.d.ts +1 -1
  51. package/dist/access-schema.js +3 -3
  52. package/dist/{access-service-DGynYlbE.d.ts → access-service-BXYrJ3pO.d.ts} +13 -10
  53. package/dist/access-service-helpers.d.ts +15 -14
  54. package/dist/access-service.d.ts +15 -14
  55. package/dist/access-service.js +42 -41
  56. package/dist/access-surface-catalog.d.ts +15 -14
  57. package/dist/access-surface-catalog.js +50 -43
  58. package/dist/access-surface-catalog.js.map +1 -1
  59. package/dist/access-wearables-meetings-surface.d.ts +4 -4
  60. package/dist/action-confidence.d.ts +1 -1
  61. package/dist/active-memory-bridge.d.ts +1 -1
  62. package/dist/active-recall.d.ts +1 -1
  63. package/dist/active-recall.js +3 -2
  64. package/dist/active-recall.js.map +1 -1
  65. package/dist/activity/digest.d.ts +1 -1
  66. package/dist/adapters/index.js +4 -4
  67. package/dist/adapters/registry.js +2 -2
  68. package/dist/ambient-provenance.d.ts +1 -1
  69. package/dist/artifact-search.d.ts +1 -1
  70. package/dist/background-generation.d.ts +1 -1
  71. package/dist/behavior-learner.d.ts +1 -1
  72. package/dist/behavior-signals.d.ts +1 -1
  73. package/dist/bootstrap.d.ts +11 -11
  74. package/dist/briefing-window.d.ts +2 -2
  75. package/dist/briefing.d.ts +2 -2
  76. package/dist/briefing.js +13 -13
  77. package/dist/buffer-surprise-report.d.ts +1 -1
  78. package/dist/buffer-turn-helpers.d.ts +1 -1
  79. package/dist/buffer.d.ts +2 -2
  80. package/dist/buffer.js +5 -5
  81. package/dist/bulk-import/index.d.ts +3 -3
  82. package/dist/calibration.d.ts +1 -1
  83. package/dist/capabilities.d.ts +1 -1
  84. package/dist/{catalog-Dl2hYqpS.d.ts → catalog-Bns9zpl3.d.ts} +1 -1
  85. package/dist/causal-behavior.d.ts +1 -1
  86. package/dist/causal-consolidation.d.ts +1 -1
  87. package/dist/causal-consolidation.js +14 -14
  88. package/dist/causal-trajectory-graph.d.ts +1 -1
  89. package/dist/{chunk-D5DTZBTV.js → chunk-2JFSQWFH.js} +6 -6
  90. package/dist/chunk-2JFSQWFH.js.map +1 -0
  91. package/dist/{chunk-5EHPB6RL.js → chunk-2QX65IEC.js} +2 -2
  92. package/dist/{chunk-F5V7KRQS.js → chunk-2WMXZYMS.js} +2 -2
  93. package/dist/{chunk-ORQ7AZMU.js → chunk-3RKVSECD.js} +8 -8
  94. package/dist/{chunk-UGGOQKO6.js → chunk-44BILQ7U.js} +93 -51
  95. package/dist/chunk-44BILQ7U.js.map +1 -0
  96. package/dist/{chunk-3NYM6JA4.js → chunk-4LPIVP77.js} +2 -2
  97. package/dist/{chunk-PW4HTWXL.js → chunk-55XWRL6F.js} +2 -2
  98. package/dist/{chunk-GGY7WKMB.js → chunk-5KBI2MOC.js} +1 -1
  99. package/dist/{chunk-IXMWTNNY.js → chunk-6A6HSXWV.js} +2 -2
  100. package/dist/{chunk-4HTQ76O4.js → chunk-7SJCSDQ3.js} +2 -2
  101. package/dist/{chunk-U4ESGVZ2.js → chunk-7YUX52XZ.js} +3 -3
  102. package/dist/{chunk-HXDXR2FH.js → chunk-AS4YKT75.js} +40 -30
  103. package/dist/chunk-AS4YKT75.js.map +1 -0
  104. package/dist/{chunk-EUP4AXIW.js → chunk-B6VDWDFN.js} +2 -2
  105. package/dist/{chunk-AN2O4KZY.js → chunk-BMENHAJL.js} +2 -2
  106. package/dist/{chunk-5ZXGH2ZL.js → chunk-BXOPUJ3W.js} +2 -2
  107. package/dist/{chunk-TE6B2UU6.js → chunk-CD7VTZIG.js} +2 -2
  108. package/dist/{chunk-EC5SVM6H.js → chunk-DE4ZGH2N.js} +2 -2
  109. package/dist/{chunk-YFVIA5A6.js → chunk-DRLZB3CN.js} +2 -2
  110. package/dist/{chunk-SRRZZHA5.js → chunk-EGOWY342.js} +2 -2
  111. package/dist/chunk-EIB4FJVR.js +46 -0
  112. package/dist/chunk-EIB4FJVR.js.map +1 -0
  113. package/dist/{chunk-W44T2KHG.js → chunk-EWBDJQ5Y.js} +3 -3
  114. package/dist/{chunk-JMBTNVMW.js → chunk-FGRJJBFO.js} +2 -2
  115. package/dist/{chunk-F3TURDFG.js → chunk-FPB5LQWW.js} +4 -1
  116. package/dist/{chunk-F3TURDFG.js.map → chunk-FPB5LQWW.js.map} +1 -1
  117. package/dist/{chunk-7BNB7KHB.js → chunk-G2SJRFGP.js} +7 -7
  118. package/dist/{chunk-WGZHSQDV.js → chunk-G2XZFGYN.js} +4 -4
  119. package/dist/{chunk-CFIHVWYZ.js → chunk-GGROFIYX.js} +2 -2
  120. package/dist/chunk-GXJSLNYO.js +74 -0
  121. package/dist/chunk-GXJSLNYO.js.map +1 -0
  122. package/dist/{chunk-XLZZLUWL.js → chunk-HG74GEVY.js} +2 -2
  123. package/dist/chunk-HMDYSGY6.js +54 -0
  124. package/dist/chunk-HMDYSGY6.js.map +1 -0
  125. package/dist/{chunk-WUUGE7QC.js → chunk-HS75MCQF.js} +4 -4
  126. package/dist/{chunk-DO6R3LIE.js → chunk-I6IJDTRP.js} +2 -2
  127. package/dist/{chunk-TZ44E732.js → chunk-IO7K7GES.js} +2 -2
  128. package/dist/{chunk-AYP2FZFA.js → chunk-JUOJGZPV.js} +2 -2
  129. package/dist/{chunk-P7S7BVBJ.js → chunk-JXLEQIJ7.js} +2 -2
  130. package/dist/{chunk-4XTSKGLX.js → chunk-JYXLW22X.js} +3 -3
  131. package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
  132. package/dist/chunk-LI4APB6M.js +218 -0
  133. package/dist/chunk-LI4APB6M.js.map +1 -0
  134. package/dist/{chunk-VGRVJ6Z6.js → chunk-LIAM7U2J.js} +2 -2
  135. package/dist/{chunk-S3SND7SO.js → chunk-LIDJWAQV.js} +2 -2
  136. package/dist/{chunk-T5NI5K46.js → chunk-LYMJ36UI.js} +2 -2
  137. package/dist/{chunk-KF57IQFX.js → chunk-MMQVWKFY.js} +19 -15
  138. package/dist/chunk-MMQVWKFY.js.map +1 -0
  139. package/dist/{chunk-QUFJ3NJE.js → chunk-MNG536IS.js} +20 -20
  140. package/dist/{chunk-SW42MMAH.js → chunk-MQT3WP3F.js} +2 -2
  141. package/dist/{chunk-UEJY6ZTQ.js → chunk-NE45P3OF.js} +2 -2
  142. package/dist/{chunk-N46SNYMC.js → chunk-NMA7RQPR.js} +2 -2
  143. package/dist/{chunk-47C6DF43.js → chunk-OS4JVTHR.js} +3 -3
  144. package/dist/{chunk-W6YDAIVB.js → chunk-OSTXIFZW.js} +13 -2
  145. package/dist/chunk-OSTXIFZW.js.map +1 -0
  146. package/dist/chunk-P3YNEMYY.js +212 -0
  147. package/dist/chunk-P3YNEMYY.js.map +1 -0
  148. package/dist/{chunk-JOP7W24J.js → chunk-PA67GXYJ.js} +8 -8
  149. package/dist/{chunk-FSH6KRKO.js → chunk-PPJLJD2D.js} +2 -2
  150. package/dist/{chunk-AG6PANBG.js → chunk-PXJ6HE2W.js} +11 -11
  151. package/dist/{chunk-DDBLZJRV.js → chunk-QB4VXCFN.js} +2 -2
  152. package/dist/{chunk-L63M2PE4.js → chunk-QGAKUOKU.js} +2 -2
  153. package/dist/{chunk-AYZHHMTK.js → chunk-QKFYJVDG.js} +2 -2
  154. package/dist/{chunk-YUQPUK6H.js → chunk-QLCMOHRE.js} +2 -2
  155. package/dist/{chunk-Q4AUIFUA.js → chunk-QMWKENNK.js} +4 -4
  156. package/dist/{chunk-GYYLZOSO.js → chunk-R4JTUTU6.js} +13 -6
  157. package/dist/chunk-R4JTUTU6.js.map +1 -0
  158. package/dist/{chunk-66QOUYNS.js → chunk-ROBGBT3D.js} +173 -56
  159. package/dist/chunk-ROBGBT3D.js.map +1 -0
  160. package/dist/{chunk-TNQR7N52.js → chunk-S3YM4SZQ.js} +2 -1
  161. package/dist/chunk-S3YM4SZQ.js.map +1 -0
  162. package/dist/{chunk-RW4QNB4J.js → chunk-S45INSV3.js} +3 -1
  163. package/dist/{chunk-RW4QNB4J.js.map → chunk-S45INSV3.js.map} +1 -1
  164. package/dist/{chunk-FMEP2RB5.js → chunk-SJ7ZNUXB.js} +2 -2
  165. package/dist/{chunk-EEHQY7ER.js → chunk-SK6HEQTW.js} +2 -2
  166. package/dist/{chunk-PGAB2V53.js → chunk-SMBWBEE7.js} +2 -2
  167. package/dist/{chunk-62NYUFNH.js → chunk-SS33RR4F.js} +8 -8
  168. package/dist/{chunk-4ECS33D4.js → chunk-T3T5HTZZ.js} +2 -2
  169. package/dist/{chunk-KG2OOAFF.js → chunk-TQSCR5DU.js} +6 -6
  170. package/dist/{chunk-OV7GJ4KW.js → chunk-V2XNMIAM.js} +2 -2
  171. package/dist/{chunk-AS2NOPDM.js → chunk-V7IONNTJ.js} +2 -2
  172. package/dist/{chunk-HYWPUYLO.js → chunk-VV5GCNKG.js} +2 -2
  173. package/dist/{chunk-Y3XHIWVP.js → chunk-WL3PGPGU.js} +2 -2
  174. package/dist/{chunk-3UQIS2GC.js → chunk-WNMVUWI7.js} +2 -2
  175. package/dist/{chunk-66YUOI53.js → chunk-WQW6KPVC.js} +1 -1
  176. package/dist/chunk-WQW6KPVC.js.map +1 -0
  177. package/dist/{chunk-2AV75BJW.js → chunk-WVI43D54.js} +24 -1
  178. package/dist/chunk-WVI43D54.js.map +1 -0
  179. package/dist/{chunk-WOBMSMUK.js → chunk-WYU66IVZ.js} +3 -3
  180. package/dist/{chunk-A7GQBJP5.js → chunk-X2NFFBTO.js} +4 -4
  181. package/dist/{chunk-CWMMKFO7.js → chunk-XEHLYA5Z.js} +3 -3
  182. package/dist/{chunk-JIWZVOQM.js → chunk-XKHRNAK7.js} +2 -2
  183. package/dist/{chunk-VW5TGNJN.js → chunk-XXLH6KFO.js} +2 -2
  184. package/dist/{chunk-J5OXWG66.js → chunk-XZUHXKM3.js} +2 -2
  185. package/dist/{chunk-K4KE5MYE.js → chunk-Y5VT7BIT.js} +2 -2
  186. package/dist/{chunk-UOREPQ3C.js → chunk-YOSLKZLZ.js} +2 -2
  187. package/dist/{chunk-S7KDHFF2.js → chunk-YXWMZAAN.js} +2 -2
  188. package/dist/{chunk-EAZ3YZKN.js → chunk-ZEIJQ4I3.js} +2 -2
  189. package/dist/{cli-Cik3H7q1.d.ts → cli-B8YyIrPR.d.ts} +5 -5
  190. package/dist/cli.d.ts +17 -16
  191. package/dist/cli.js +89 -84
  192. package/dist/coding/export-okf-codegraph.d.ts +1 -1
  193. package/dist/coding/export-okf-codegraph.js +12 -12
  194. package/dist/coding/pre-action-gate.d.ts +1 -1
  195. package/dist/compounding/engine.d.ts +2 -2
  196. package/dist/compounding/engine.js +13 -13
  197. package/dist/compounding/preference-consolidator.d.ts +1 -1
  198. package/dist/compression-optimizer.d.ts +1 -1
  199. package/dist/config.d.ts +1 -1
  200. package/dist/config.js +3 -2
  201. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  202. package/dist/connectors/codex-materialize-runner.js +13 -13
  203. package/dist/connectors/codex-materialize.d.ts +1 -1
  204. package/dist/connectors/index.d.ts +1 -1
  205. package/dist/connectors/index.js +13 -13
  206. package/dist/consolidation-provenance-check.d.ts +2 -2
  207. package/dist/consolidation-undo.d.ts +2 -2
  208. package/dist/contradiction/index.d.ts +4 -4
  209. package/dist/converge-config.d.ts +1 -1
  210. package/dist/convergence-refresh.d.ts +2 -2
  211. package/dist/convergence-refresh.js +15 -15
  212. package/dist/conversation-index/backend.d.ts +1 -1
  213. package/dist/conversation-index/chunker.d.ts +1 -1
  214. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  215. package/dist/conversation-index/indexer.d.ts +1 -1
  216. package/dist/conversation-index/search.d.ts +1 -1
  217. package/dist/corpus-watermark.d.ts +1 -1
  218. package/dist/corpus-watermark.js +15 -15
  219. package/dist/cross-lingual-recall.d.ts +1 -1
  220. package/dist/day-summary.d.ts +1 -1
  221. package/dist/deep-recall-http-glue.d.ts +15 -14
  222. package/dist/deep-recall-http-glue.js +4 -4
  223. package/dist/deep-recall-mcp-tools.d.ts +15 -14
  224. package/dist/deep-recall-policy-llm.d.ts +2 -2
  225. package/dist/deep-recall-seeds.d.ts +1 -1
  226. package/dist/deep-recall.js +2 -2
  227. package/dist/delinearize.d.ts +1 -1
  228. package/dist/dependency-propagation-config.d.ts +1 -1
  229. package/dist/{dependency-propagation-delivery-BXaUoVUf.d.ts → dependency-propagation-delivery-B-LvcDaq.d.ts} +1 -1
  230. package/dist/direct-answer-wiring.d.ts +1 -1
  231. package/dist/direct-answer.d.ts +1 -1
  232. package/dist/embedding-fallback.d.ts +1 -1
  233. package/dist/enrichment/index.d.ts +1 -1
  234. package/dist/entity-origin-fields.d.ts +1 -1
  235. package/dist/entity-retrieval.d.ts +2 -2
  236. package/dist/entity-retrieval.js +13 -13
  237. package/dist/entity-schema.d.ts +1 -1
  238. package/dist/episodic-context.d.ts +1 -1
  239. package/dist/eval-trajectory-contracts.d.ts +1 -1
  240. package/dist/eval-trajectory-store.d.ts +1 -1
  241. package/dist/eval-trajectory.d.ts +1 -1
  242. package/dist/explicit-capture.d.ts +11 -11
  243. package/dist/external-wiki-access.d.ts +15 -14
  244. package/dist/external-wiki-access.js +43 -42
  245. package/dist/external-wiki-collection-registration.d.ts +1 -1
  246. package/dist/external-wiki-collection.d.ts +1 -1
  247. package/dist/external-wiki-mcp-tools.d.ts +15 -14
  248. package/dist/extraction-error-classification.d.ts +1 -1
  249. package/dist/extraction-evidence-class.d.ts +142 -0
  250. package/dist/extraction-evidence-class.js +113 -0
  251. package/dist/extraction-evidence-class.js.map +1 -0
  252. package/dist/extraction-faithfulness.d.ts +2 -2
  253. package/dist/extraction-judge-telemetry.d.ts +2 -2
  254. package/dist/extraction-judge-training.d.ts +2 -2
  255. package/dist/extraction-judge.d.ts +2 -2
  256. package/dist/extraction-liveness.d.ts +1 -1
  257. package/dist/extraction-normalization.d.ts +1 -1
  258. package/dist/extraction-prompt.d.ts +1 -1
  259. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  260. package/dist/extraction-source-grounding.d.ts +1 -1
  261. package/dist/extraction-source-grounding.js +2 -2
  262. package/dist/extraction-span-config.d.ts +1 -1
  263. package/dist/extraction-span-materialize.d.ts +1 -1
  264. package/dist/extraction-span-wire.d.ts +1 -1
  265. package/dist/extraction.d.ts +2 -2
  266. package/dist/extraction.js +21 -21
  267. package/dist/fallback-llm-codex-subscription.d.ts +1 -1
  268. package/dist/fallback-llm.d.ts +1 -1
  269. package/dist/{generic-recall-paths-BkVVEoeO.d.ts → generic-recall-paths-Bfc7zigS.d.ts} +1 -1
  270. package/dist/graph-append-error.d.ts +1 -1
  271. package/dist/graph-dashboard-diff.d.ts +1 -1
  272. package/dist/graph-dashboard-key.d.ts +1 -1
  273. package/dist/graph-dashboard-parser.d.ts +1 -1
  274. package/dist/graph-edge-reinforcement.d.ts +1 -1
  275. package/dist/graph-jsonl.d.ts +1 -1
  276. package/dist/graph-path-reconstruction.d.ts +1 -1
  277. package/dist/graph-path-scoring.d.ts +1 -1
  278. package/dist/graph-snapshot.d.ts +1 -1
  279. package/dist/graph.d.ts +1 -1
  280. package/dist/harmonic-retrieval.js +1 -1
  281. package/dist/importance.d.ts +1 -1
  282. package/dist/importers/index.d.ts +1 -1
  283. package/dist/importers/test-utils.d.ts +1 -1
  284. package/dist/in-flight-reads.d.ts +1 -1
  285. package/dist/{index-Zb6SUV4R.d.ts → index-C7IA1DxB.d.ts} +3 -3
  286. package/dist/index.d.ts +121 -30
  287. package/dist/index.js +179 -133
  288. package/dist/intent.d.ts +1 -1
  289. package/dist/lcm/engine.d.ts +1 -1
  290. package/dist/lcm/engine.js +2 -2
  291. package/dist/lcm/index.d.ts +1 -1
  292. package/dist/lcm/index.js +7 -7
  293. package/dist/lcm/tools.d.ts +1 -1
  294. package/dist/lifecycle.d.ts +1 -1
  295. package/dist/live-connectors-runner.d.ts +1 -1
  296. package/dist/{local-llm-DdrhmecM.d.ts → local-llm-DH4tWtck.d.ts} +1 -1
  297. package/dist/local-llm-helpers.d.ts +2 -2
  298. package/dist/local-llm.d.ts +2 -2
  299. package/dist/local-llm.js +2 -2
  300. package/dist/local-model-endpoint.d.ts +1 -1
  301. package/dist/location/index.d.ts +5 -5
  302. package/dist/location/index.js +15 -15
  303. package/dist/maintenance/memory-governance.d.ts +1 -1
  304. package/dist/maintenance/memory-governance.js +13 -13
  305. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  306. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +13 -13
  307. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  308. package/dist/maintenance/rebuild-memory-projection.js +14 -14
  309. package/dist/{maintenance-Du_VRvVg.d.ts → maintenance-n6yeTQhR.d.ts} +4 -4
  310. package/dist/mcp-memory-inspector-app.d.ts +15 -14
  311. package/dist/mcp-read-only-tools.js +1 -1
  312. package/dist/memory-action-policy.d.ts +1 -1
  313. package/dist/memory-browse-http-glue.d.ts +170 -0
  314. package/dist/memory-browse-http-glue.js +9 -0
  315. package/dist/memory-browse-http-glue.js.map +1 -0
  316. package/dist/memory-browse-mcp-tools.d.ts +160 -0
  317. package/dist/memory-browse-mcp-tools.js +8 -0
  318. package/dist/memory-browse-mcp-tools.js.map +1 -0
  319. package/dist/memory-browse-operations.d.ts +2 -0
  320. package/dist/memory-browse-operations.js +246 -0
  321. package/dist/memory-browse-operations.js.map +1 -0
  322. package/dist/memory-browse.d.ts +113 -0
  323. package/dist/memory-browse.js +49 -0
  324. package/dist/memory-browse.js.map +1 -0
  325. package/dist/memory-cache.d.ts +1 -1
  326. package/dist/memory-controller.d.ts +1 -1
  327. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  328. package/dist/memory-projection-store.d.ts +1 -1
  329. package/dist/memory-provenance.d.ts +1 -1
  330. package/dist/memory-snapshot.d.ts +1 -1
  331. package/dist/memory-subject-cli.d.ts +15 -14
  332. package/dist/memory-subject.d.ts +15 -14
  333. package/dist/memory-worth-outcomes.d.ts +2 -2
  334. package/dist/models-json.d.ts +1 -1
  335. package/dist/namespaces/migrate.d.ts +3 -3
  336. package/dist/namespaces/migrate.js +14 -14
  337. package/dist/namespaces/principal.d.ts +1 -1
  338. package/dist/namespaces/search.d.ts +1 -1
  339. package/dist/namespaces/search.js +6 -6
  340. package/dist/namespaces/storage.d.ts +3 -3
  341. package/dist/namespaces/storage.js +13 -13
  342. package/dist/native-knowledge.d.ts +1 -1
  343. package/dist/offline-sync-impression-drain.d.ts +1 -1
  344. package/dist/operator-doctor-corpus.d.ts +1 -1
  345. package/dist/operator-doctor-corpus.js +16 -16
  346. package/dist/operator-doctor-replica.d.ts +1 -1
  347. package/dist/operator-toolkit.d.ts +3 -3
  348. package/dist/operator-toolkit.js +23 -22
  349. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  350. package/dist/orchestration/maintenance.d.ts +4 -4
  351. package/dist/orchestration/maintenance.js +23 -23
  352. package/dist/{orchestrator-D9U3Zgjp.d.ts → orchestrator-Cirjo3ZB.d.ts} +10 -10
  353. package/dist/orchestrator.d.ts +11 -11
  354. package/dist/orchestrator.js +121 -116
  355. package/dist/patterns-cli.d.ts +1 -1
  356. package/dist/{pipeline-4lqKJZ_0.d.ts → pipeline-DK79FrgH.d.ts} +1 -1
  357. package/dist/policy-runtime.d.ts +1 -1
  358. package/dist/proactive-contention.d.ts +2 -2
  359. package/dist/provenance.d.ts +77 -2
  360. package/dist/provenance.js +7 -3
  361. package/dist/{public-http-AabioFLh.d.ts → public-http-VB85y0ZH.d.ts} +1 -1
  362. package/dist/{qmd-DnoWaXKb.d.ts → qmd-BT0ErlRS.d.ts} +1 -1
  363. package/dist/qmd-preflight.d.ts +2 -2
  364. package/dist/qmd-recall-cache.d.ts +1 -1
  365. package/dist/qmd.d.ts +2 -2
  366. package/dist/qmd.js +4 -4
  367. package/dist/recall-concurrency-config.d.ts +1 -1
  368. package/dist/recall-disclosure-escalation.d.ts +1 -1
  369. package/dist/recall-explain-renderer.d.ts +1 -1
  370. package/dist/recall-explain-renderer.js +3 -3
  371. package/dist/recall-memory-map.d.ts +1 -1
  372. package/dist/recall-navigate-access.d.ts +1 -1
  373. package/dist/recall-navigate-access.js +2 -2
  374. package/dist/recall-navigate-disclosure.d.ts +1 -1
  375. package/dist/recall-navigate-disclosure.js +2 -2
  376. package/dist/recall-navigate.d.ts +1 -1
  377. package/dist/recall-navigate.js +2 -2
  378. package/dist/recall-navigation-http-glue.d.ts +15 -14
  379. package/dist/recall-navigation-mcp-tools.d.ts +15 -14
  380. package/dist/recall-navigation-operations.js +43 -42
  381. package/dist/recall-navigation-renderer.d.ts +1 -1
  382. package/dist/recall-navigation.d.ts +1 -1
  383. package/dist/recall-navigation.js +42 -41
  384. package/dist/recall-planner-llm.d.ts +1 -1
  385. package/dist/recall-state-view-admission.d.ts +2 -2
  386. package/dist/recall-state-view-admission.js +5 -5
  387. package/dist/recall-state-view-anchors.d.ts +1 -1
  388. package/dist/recall-state.d.ts +1 -1
  389. package/dist/recall-tag-filter.d.ts +1 -1
  390. package/dist/recall-timings.d.ts +1 -1
  391. package/dist/recall-xray-cli.d.ts +1 -1
  392. package/dist/recall-xray-cli.js +4 -4
  393. package/dist/recall-xray-renderer.d.ts +1 -1
  394. package/dist/recall-xray-renderer.js +3 -3
  395. package/dist/recall-xray.d.ts +1 -1
  396. package/dist/recall-xray.js +2 -2
  397. package/dist/reconcile/cursor.d.ts +1 -1
  398. package/dist/reconcile/manifest.d.ts +4 -1
  399. package/dist/reconcile/manifest.js +1 -1
  400. package/dist/reconcile/plan.d.ts +1 -1
  401. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  402. package/dist/replay/normalizers/claude.d.ts +1 -1
  403. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  404. package/dist/replay/normalizers/shared.d.ts +1 -1
  405. package/dist/replay/runner.d.ts +1 -1
  406. package/dist/replay/types.d.ts +1 -1
  407. package/dist/replica-divergence.d.ts +1 -1
  408. package/dist/replica-peers-config.d.ts +1 -1
  409. package/dist/resolve-auth-token.d.ts +1 -1
  410. package/dist/resume-bundles.js +4 -3
  411. package/dist/retrieval-agents.d.ts +2 -2
  412. package/dist/retrieval-tiers.d.ts +1 -1
  413. package/dist/routing/engine.d.ts +1 -1
  414. package/dist/routing/store.d.ts +1 -1
  415. package/dist/salvage-envelope.d.ts +1 -1
  416. package/dist/{scope-profiles-crazXegZ.d.ts → scope-profiles-BgPQx9iE.d.ts} +1 -1
  417. package/dist/search/embed-helper.d.ts +1 -1
  418. package/dist/search/factory.d.ts +1 -1
  419. package/dist/search/factory.js +5 -5
  420. package/dist/search/index.d.ts +1 -1
  421. package/dist/search/index.js +9 -9
  422. package/dist/search/lancedb-backend.d.ts +1 -1
  423. package/dist/search/meilisearch-backend.d.ts +1 -1
  424. package/dist/search/noop-backend.d.ts +1 -1
  425. package/dist/search/orama-backend.d.ts +1 -1
  426. package/dist/search/port.d.ts +1 -1
  427. package/dist/search/remote-backend.d.ts +1 -1
  428. package/dist/{semantic-consolidation-B09JI9WM.d.ts → semantic-consolidation-BrAWKVcm.d.ts} +1 -1
  429. package/dist/semantic-consolidation.d.ts +2 -2
  430. package/dist/semantic-consolidation.js +14 -14
  431. package/dist/semantic-rule-promotion.js +13 -13
  432. package/dist/semantic-rule-verifier.d.ts +1 -1
  433. package/dist/semantic-rule-verifier.js +13 -13
  434. package/dist/{service-ByjbCuj7.d.ts → service-CqHOTg87.d.ts} +2 -2
  435. package/dist/session-observer-bands.d.ts +1 -1
  436. package/dist/session-observer-state.d.ts +1 -1
  437. package/dist/shared-context/manager.d.ts +1 -1
  438. package/dist/signal.d.ts +1 -1
  439. package/dist/source-agent-qualifier.d.ts +1 -1
  440. package/dist/source-agent-qualifier.js +17 -17
  441. package/dist/standing-memory-block.d.ts +77 -0
  442. package/dist/standing-memory-block.js +40 -0
  443. package/dist/standing-memory-block.js.map +1 -0
  444. package/dist/standup/build.js +13 -13
  445. package/dist/{storage-CbdF077z.d.ts → storage-mD2YQ8_2.d.ts} +1 -1
  446. package/dist/storage.d.ts +2 -2
  447. package/dist/storage.js +12 -12
  448. package/dist/subject-config.d.ts +1 -1
  449. package/dist/summarizer.d.ts +1 -1
  450. package/dist/summarizer.js +3 -3
  451. package/dist/summary-snapshot.d.ts +1 -1
  452. package/dist/support-passport/index.d.ts +18 -17
  453. package/dist/support-passport/index.js +45 -44
  454. package/dist/{tagging-CF7DNV7_.d.ts → tagging-CiAVA-ID.d.ts} +2 -2
  455. package/dist/temporal-supersession.d.ts +2 -2
  456. package/dist/temporal-timeline-recall.d.ts +1 -1
  457. package/dist/temporal-validity.d.ts +1 -1
  458. package/dist/threading.d.ts +1 -1
  459. package/dist/tier-migration.d.ts +2 -2
  460. package/dist/tier-routing.d.ts +1 -1
  461. package/dist/topics.d.ts +1 -1
  462. package/dist/transcript.d.ts +1 -1
  463. package/dist/transfer/autodetect.js +1 -1
  464. package/dist/transfer/backup.js +1 -1
  465. package/dist/transfer/capsule-export.js +2 -2
  466. package/dist/transfer/export-okf.d.ts +1 -1
  467. package/dist/transfer/export-okf.js +13 -13
  468. package/dist/trust-score-stage.d.ts +1 -1
  469. package/dist/trust-score.d.ts +1 -1
  470. package/dist/{types-nmhnJpxx.d.ts → types-DgDaoY61.d.ts} +6 -0
  471. package/dist/types.d.ts +1 -1
  472. package/dist/types.js +1 -1
  473. package/dist/utility-runtime.d.ts +1 -1
  474. package/dist/verified-recall.js +13 -13
  475. package/dist/who-knows-cli.d.ts +15 -14
  476. package/dist/who-knows-cli.js +14 -14
  477. package/dist/who-knows.d.ts +15 -14
  478. package/dist/who-knows.js +13 -13
  479. package/dist/write-envelope.d.ts +1 -1
  480. package/package.json +2 -2
  481. package/src/access-boundary.ts +3 -0
  482. package/src/access-http.ts +2 -2
  483. package/src/access-mcp-output-schemas.ts +25 -0
  484. package/src/access-mcp.ts +3 -3
  485. package/src/access-operations.ts +2 -0
  486. package/src/access-service.ts +6 -1
  487. package/src/access-surface-catalog.test.ts +20 -0
  488. package/src/access-surface-catalog.ts +6 -0
  489. package/src/activity/timeline/index.ts +1 -0
  490. package/src/activity/timeline/journal-recap.test.ts +17 -0
  491. package/src/activity/timeline/journal-recap.ts +19 -6
  492. package/src/activity/timeline/recap-export-range.test.ts +289 -0
  493. package/src/activity/timeline/recap-export-range.ts +169 -0
  494. package/src/activity/timeline/recap-export.test.ts +73 -1
  495. package/src/activity/timeline/recap-export.ts +57 -4
  496. package/src/cli/memory-browse-commands.ts +77 -0
  497. package/src/cli.ts +2 -2
  498. package/src/config.ts +11 -0
  499. package/src/extraction-evidence-class.test.ts +355 -0
  500. package/src/extraction-evidence-class.ts +241 -0
  501. package/src/index.ts +1 -0
  502. package/src/mcp-read-only-tools.ts +3 -0
  503. package/src/memory-browse-http-glue.ts +93 -0
  504. package/src/memory-browse-mcp-tools.ts +57 -0
  505. package/src/memory-browse-operations.ts +88 -0
  506. package/src/memory-browse-surface.test.ts +500 -0
  507. package/src/memory-browse.ts +316 -0
  508. package/src/provenance.ts +3 -3
  509. package/src/reconcile/manifest.test.ts +27 -0
  510. package/src/reconcile/manifest.ts +7 -0
  511. package/src/standing-memory-block.test.ts +262 -0
  512. package/src/standing-memory-block.ts +331 -0
  513. package/src/types.ts +6 -0
  514. package/dist/chunk-2AV75BJW.js.map +0 -1
  515. package/dist/chunk-66QOUYNS.js.map +0 -1
  516. package/dist/chunk-66YUOI53.js.map +0 -1
  517. package/dist/chunk-D5DTZBTV.js.map +0 -1
  518. package/dist/chunk-GYYLZOSO.js.map +0 -1
  519. package/dist/chunk-HXDXR2FH.js.map +0 -1
  520. package/dist/chunk-KF57IQFX.js.map +0 -1
  521. package/dist/chunk-TNQR7N52.js.map +0 -1
  522. package/dist/chunk-UGGOQKO6.js.map +0 -1
  523. package/dist/chunk-W6YDAIVB.js.map +0 -1
  524. /package/dist/{chunk-5EHPB6RL.js.map → chunk-2QX65IEC.js.map} +0 -0
  525. /package/dist/{chunk-F5V7KRQS.js.map → chunk-2WMXZYMS.js.map} +0 -0
  526. /package/dist/{chunk-ORQ7AZMU.js.map → chunk-3RKVSECD.js.map} +0 -0
  527. /package/dist/{chunk-3NYM6JA4.js.map → chunk-4LPIVP77.js.map} +0 -0
  528. /package/dist/{chunk-PW4HTWXL.js.map → chunk-55XWRL6F.js.map} +0 -0
  529. /package/dist/{chunk-GGY7WKMB.js.map → chunk-5KBI2MOC.js.map} +0 -0
  530. /package/dist/{chunk-IXMWTNNY.js.map → chunk-6A6HSXWV.js.map} +0 -0
  531. /package/dist/{chunk-4HTQ76O4.js.map → chunk-7SJCSDQ3.js.map} +0 -0
  532. /package/dist/{chunk-U4ESGVZ2.js.map → chunk-7YUX52XZ.js.map} +0 -0
  533. /package/dist/{chunk-EUP4AXIW.js.map → chunk-B6VDWDFN.js.map} +0 -0
  534. /package/dist/{chunk-AN2O4KZY.js.map → chunk-BMENHAJL.js.map} +0 -0
  535. /package/dist/{chunk-5ZXGH2ZL.js.map → chunk-BXOPUJ3W.js.map} +0 -0
  536. /package/dist/{chunk-TE6B2UU6.js.map → chunk-CD7VTZIG.js.map} +0 -0
  537. /package/dist/{chunk-EC5SVM6H.js.map → chunk-DE4ZGH2N.js.map} +0 -0
  538. /package/dist/{chunk-YFVIA5A6.js.map → chunk-DRLZB3CN.js.map} +0 -0
  539. /package/dist/{chunk-SRRZZHA5.js.map → chunk-EGOWY342.js.map} +0 -0
  540. /package/dist/{chunk-W44T2KHG.js.map → chunk-EWBDJQ5Y.js.map} +0 -0
  541. /package/dist/{chunk-JMBTNVMW.js.map → chunk-FGRJJBFO.js.map} +0 -0
  542. /package/dist/{chunk-7BNB7KHB.js.map → chunk-G2SJRFGP.js.map} +0 -0
  543. /package/dist/{chunk-WGZHSQDV.js.map → chunk-G2XZFGYN.js.map} +0 -0
  544. /package/dist/{chunk-CFIHVWYZ.js.map → chunk-GGROFIYX.js.map} +0 -0
  545. /package/dist/{chunk-XLZZLUWL.js.map → chunk-HG74GEVY.js.map} +0 -0
  546. /package/dist/{chunk-WUUGE7QC.js.map → chunk-HS75MCQF.js.map} +0 -0
  547. /package/dist/{chunk-DO6R3LIE.js.map → chunk-I6IJDTRP.js.map} +0 -0
  548. /package/dist/{chunk-TZ44E732.js.map → chunk-IO7K7GES.js.map} +0 -0
  549. /package/dist/{chunk-AYP2FZFA.js.map → chunk-JUOJGZPV.js.map} +0 -0
  550. /package/dist/{chunk-P7S7BVBJ.js.map → chunk-JXLEQIJ7.js.map} +0 -0
  551. /package/dist/{chunk-4XTSKGLX.js.map → chunk-JYXLW22X.js.map} +0 -0
  552. /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
  553. /package/dist/{chunk-VGRVJ6Z6.js.map → chunk-LIAM7U2J.js.map} +0 -0
  554. /package/dist/{chunk-S3SND7SO.js.map → chunk-LIDJWAQV.js.map} +0 -0
  555. /package/dist/{chunk-T5NI5K46.js.map → chunk-LYMJ36UI.js.map} +0 -0
  556. /package/dist/{chunk-QUFJ3NJE.js.map → chunk-MNG536IS.js.map} +0 -0
  557. /package/dist/{chunk-SW42MMAH.js.map → chunk-MQT3WP3F.js.map} +0 -0
  558. /package/dist/{chunk-UEJY6ZTQ.js.map → chunk-NE45P3OF.js.map} +0 -0
  559. /package/dist/{chunk-N46SNYMC.js.map → chunk-NMA7RQPR.js.map} +0 -0
  560. /package/dist/{chunk-47C6DF43.js.map → chunk-OS4JVTHR.js.map} +0 -0
  561. /package/dist/{chunk-JOP7W24J.js.map → chunk-PA67GXYJ.js.map} +0 -0
  562. /package/dist/{chunk-FSH6KRKO.js.map → chunk-PPJLJD2D.js.map} +0 -0
  563. /package/dist/{chunk-AG6PANBG.js.map → chunk-PXJ6HE2W.js.map} +0 -0
  564. /package/dist/{chunk-DDBLZJRV.js.map → chunk-QB4VXCFN.js.map} +0 -0
  565. /package/dist/{chunk-L63M2PE4.js.map → chunk-QGAKUOKU.js.map} +0 -0
  566. /package/dist/{chunk-AYZHHMTK.js.map → chunk-QKFYJVDG.js.map} +0 -0
  567. /package/dist/{chunk-YUQPUK6H.js.map → chunk-QLCMOHRE.js.map} +0 -0
  568. /package/dist/{chunk-Q4AUIFUA.js.map → chunk-QMWKENNK.js.map} +0 -0
  569. /package/dist/{chunk-FMEP2RB5.js.map → chunk-SJ7ZNUXB.js.map} +0 -0
  570. /package/dist/{chunk-EEHQY7ER.js.map → chunk-SK6HEQTW.js.map} +0 -0
  571. /package/dist/{chunk-PGAB2V53.js.map → chunk-SMBWBEE7.js.map} +0 -0
  572. /package/dist/{chunk-62NYUFNH.js.map → chunk-SS33RR4F.js.map} +0 -0
  573. /package/dist/{chunk-4ECS33D4.js.map → chunk-T3T5HTZZ.js.map} +0 -0
  574. /package/dist/{chunk-KG2OOAFF.js.map → chunk-TQSCR5DU.js.map} +0 -0
  575. /package/dist/{chunk-OV7GJ4KW.js.map → chunk-V2XNMIAM.js.map} +0 -0
  576. /package/dist/{chunk-AS2NOPDM.js.map → chunk-V7IONNTJ.js.map} +0 -0
  577. /package/dist/{chunk-HYWPUYLO.js.map → chunk-VV5GCNKG.js.map} +0 -0
  578. /package/dist/{chunk-Y3XHIWVP.js.map → chunk-WL3PGPGU.js.map} +0 -0
  579. /package/dist/{chunk-3UQIS2GC.js.map → chunk-WNMVUWI7.js.map} +0 -0
  580. /package/dist/{chunk-WOBMSMUK.js.map → chunk-WYU66IVZ.js.map} +0 -0
  581. /package/dist/{chunk-A7GQBJP5.js.map → chunk-X2NFFBTO.js.map} +0 -0
  582. /package/dist/{chunk-CWMMKFO7.js.map → chunk-XEHLYA5Z.js.map} +0 -0
  583. /package/dist/{chunk-JIWZVOQM.js.map → chunk-XKHRNAK7.js.map} +0 -0
  584. /package/dist/{chunk-VW5TGNJN.js.map → chunk-XXLH6KFO.js.map} +0 -0
  585. /package/dist/{chunk-J5OXWG66.js.map → chunk-XZUHXKM3.js.map} +0 -0
  586. /package/dist/{chunk-K4KE5MYE.js.map → chunk-Y5VT7BIT.js.map} +0 -0
  587. /package/dist/{chunk-UOREPQ3C.js.map → chunk-YOSLKZLZ.js.map} +0 -0
  588. /package/dist/{chunk-S7KDHFF2.js.map → chunk-YXWMZAAN.js.map} +0 -0
  589. /package/dist/{chunk-EAZ3YZKN.js.map → chunk-ZEIJQ4I3.js.map} +0 -0
@@ -149,6 +149,7 @@ async function buildReconcileManifest(options) {
149
149
  }
150
150
  }
151
151
  for (let i = 0; i < uncached.length; i += MANIFEST_BUILD_BATCH_SIZE) {
152
+ options.signal?.throwIfAborted();
152
153
  const batch = uncached.slice(i, i + MANIFEST_BUILD_BATCH_SIZE);
153
154
  const results = await Promise.all(
154
155
  batch.map(
@@ -347,4 +348,4 @@ export {
347
348
  buildReconcileManifest,
348
349
  collapseActiveFactDuplicates
349
350
  };
350
- //# sourceMappingURL=chunk-TNQR7N52.js.map
351
+ //# sourceMappingURL=chunk-S3YM4SZQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { computeLegacyContentHash, normalizeLegacyContent } from \"../content-hash.js\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { DEFAULT_CITATION_FORMAT, stripCitationForTemplate } from \"../source-attribution.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport { storedContentIdentityCandidates } from \"../structured-attributes.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 compareReconcilePlanEntries,\n semanticAgreementKey,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\nexport const CONTENT_HASH_NORMALIZER_VERSION = 4;\n/**\n * Version of the identity-resolution LOGIC (alias recovery, candidate\n * ordering), independent of the content normalizer: a cached manifest entry\n * written before an identity-resolution change carries an older value and is\n * reparsed even when its normalizerVersion still matches (issue #2367\n * review round 2). Bump when parsedMemoryIdentity's output shape or\n * semantics change.\n */\nexport const IDENTITY_RESOLUTION_VERSION = 2;\n\n/**\n * Identity of the manifest-affecting implementation this peer builds\n * STREAMED manifests with. Advertised via offline-sync capabilities so the\n * client can key peer-side plan caches on it: a peer that upgrades its\n * normalization logic or citation template without rewriting stored files\n * keeps the same file hashes, and only this revision invalidates the stale\n * cached identities (#2803 / #2956 review).\n */\nexport function peerManifestRevision(citationTemplate?: string): string {\n return `schema=${RECONCILE_MANIFEST_SCHEMA_VERSION};normalizer=${CONTENT_HASH_NORMALIZER_VERSION};identity=${IDENTITY_RESOLUTION_VERSION};citation=${citationTemplateFingerprint(citationTemplate)}`;\n}\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n /**\n * Recovered current-normalizer identities for a record whose persisted hash\n * is ambiguous under the legacy normalizer (issue #2367). The persisted\n * hash stays primary; these participate in duplicate bucketing so a\n * replica carrying the current form of the same fact still collapses.\n */\n contentHashAliases?: readonly string[];\n /** See IDENTITY_RESOLUTION_VERSION; absent on pre-versioning cache entries. */\n normalizerVersion?: number;\n identityResolutionVersion?: number;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n /** Present on sha-only (negative) entries so an upgrade re-parses them. */\n normalizerVersion?: number;\n identityResolutionVersion?: number;\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 = (raw: string) => { 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 citationTemplate?: string;\n /** Cancellation (#2954): checked between read batches so an aborted run\n * stops scheduling new file reads instead of walking the whole corpus. */\n signal?: AbortSignal;\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\nfunction parsedMemoryIdentity(\n filePath: string,\n raw: Buffer | string,\n parseMemory: ReconcileMemoryParser,\n citationTemplate: string\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\n const storedHash =\n parsed.frontmatter.contentHash && SHA256_PATTERN.test(parsed.frontmatter.contentHash)\n ? parsed.frontmatter.contentHash.toLowerCase()\n : undefined;\n const candidates = storedContentIdentityCandidates(parsed.content, (content) =>\n stripCitationForTemplate(content, citationTemplate)\n );\n const bodyHash = ContentHashIndex.computeHash(candidates[0] ?? \"\");\n const currentMatch = candidates.find((content) => ContentHashIndex.computeHash(content) === storedHash);\n const legacyMatch = [...candidates].reverse().find((content) => computeLegacyContentHash(content) === storedHash);\n let contentHash: string;\n let contentHashAliases: string[] | undefined;\n if (currentMatch) {\n contentHash = ContentHashIndex.computeHash(currentMatch);\n } else if (legacyMatch && normalizeLegacyContent(legacyMatch).length > 0) {\n // A non-empty legacy equality is ambiguous (issue #2367): the persisted\n // hash may be an explicit contentHashSource identity that merely collides\n // with the body's ASCII skeleton under the lossy legacy normalizer, and\n // nothing in the record proves which it is. Keep the persisted hash as\n // the primary identity and expose the recovered current identity as an\n // alias so replicas carrying either form still collapse.\n contentHash = storedHash ?? bodyHash;\n const recovered = ContentHashIndex.computeHash(legacyMatch);\n if (recovered !== contentHash) contentHashAliases = [recovered];\n } else {\n // An EMPTY legacy skeleton identifies nothing (every non-ASCII body maps\n // to it), so preserving it would collapse unrelated records. Recovering\n // the current identity here is repair, not replacement.\n contentHash = legacyMatch ? ContentHashIndex.computeHash(legacyMatch) : (storedHash ?? bodyHash);\n }\n\n return {\n id: parsed.frontmatter.id,\n category: parsed.frontmatter.category,\n contentHash,\n ...(contentHashAliases ? { contentHashAliases } : {}),\n normalizerVersion: CONTENT_HASH_NORMALIZER_VERSION,\n identityResolutionVersion: IDENTITY_RESOLUTION_VERSION,\n status: inferMemoryStatus(parsed.frontmatter, filePath),\n };\n}\n\n/**\n * Stable fingerprint of the citation template that `parsedMemoryIdentity` uses\n * to strip inline attribution before hashing. A persisted identity cache is\n * only reusable while this matches: identical file content under a different\n * template yields a different `contentHash`, which would silently change\n * duplicate and conflict decisions on a warm plan.\n */\nexport function citationTemplateFingerprint(citationTemplate?: string): string {\n return createHash(\"sha256\")\n .update(citationTemplate ?? DEFAULT_CITATION_FORMAT)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\n/**\n * Structural guard for identities read back from an on-disk cache. A cache file\n * is untrusted JSON: without this an entry carrying `memory: null` survives the\n * loader's cast and then throws when the hit path dereferences\n * `memory.normalizerVersion`, aborting a manifest build that should instead\n * fall back to a cold parse.\n */\nexport function isReconcileMemoryIdentity(value: unknown): value is ReconcileMemoryIdentity {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const memory = value as Record<string, unknown>;\n if (typeof memory.id !== \"string\") return false;\n if (typeof memory.category !== \"string\") return false;\n if (typeof memory.contentHash !== \"string\" || !SHA256_PATTERN.test(memory.contentHash)) return false;\n if (typeof memory.status !== \"string\") return false;\n if (memory.normalizerVersion !== undefined && typeof memory.normalizerVersion !== \"number\") return false;\n if (memory.identityResolutionVersion !== undefined && typeof memory.identityResolutionVersion !== \"number\") {\n return false;\n }\n if (\n memory.contentHashAliases !== undefined &&\n (!Array.isArray(memory.contentHashAliases) ||\n memory.contentHashAliases.some((alias) => typeof alias !== \"string\" || !SHA256_PATTERN.test(alias)))\n ) {\n return false;\n }\n return true;\n}\n\n/** A cache hit is valid only when the entry was built by the current parser.\n * Positive hits carry versions on `memory`; sha-only negatives stamp the same\n * versions on the entry itself. Unversioned negatives are pre-upgrade leftovers\n * and must miss so a newer parser can recognize an identity it previously skipped. */\nexport function isCachedIdentityReusable(cached: ReconcileManifestFile): boolean {\n if (cached.memory !== undefined) {\n return (\n cached.memory.normalizerVersion === CONTENT_HASH_NORMALIZER_VERSION &&\n cached.memory.identityResolutionVersion === IDENTITY_RESOLUTION_VERSION\n );\n }\n return (\n cached.normalizerVersion === CONTENT_HASH_NORMALIZER_VERSION &&\n cached.identityResolutionVersion === IDENTITY_RESOLUTION_VERSION\n );\n}\n\nexport async function buildReconcileManifestFile(\n file: ReconcileFileState,\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>,\n parseMemory: ReconcileMemoryParser,\n citationTemplate = DEFAULT_CITATION_FORMAT\n): Promise<ReconcileManifestFile> {\n let raw: Buffer | string | null = null;\n let readFailed = false;\n if (isMemoryPath(file.path)) {\n try {\n raw = await readFile(file);\n } catch {\n readFailed = true;\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, citationTemplate);\n // A thrown read is not a negative identity. Stamping versions here would\n // make the next warm cycle treat the failure as \"this file has no id\".\n if (readFailed) return { ...file };\n return {\n ...file,\n ...(memory\n ? { memory }\n : {\n normalizerVersion: CONTENT_HASH_NORMALIZER_VERSION,\n identityResolutionVersion: IDENTITY_RESOLUTION_VERSION,\n }),\n };\n}\n\n/** Read-batch size for manifest builds. Uncached files are independent (a\n * per-file read + sha256 + frontmatter parse); processing them in bounded\n * batches amortizes the per-file promise/async-hook overhead that dominates\n * boot-scale corpus builds (measured: ~66% of CPU in async_hooks._propagate +\n * GC at ~100k files) and overlaps IO waits, while the bound keeps memory flat.\n * Order is preserved: results land in input order regardless of completion\n * order, so the manifest output is byte-identical to the sequential form. */\nconst MANIFEST_BUILD_BATCH_SIZE = 50;\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 allFiles = [...options.files];\n // Every result — cache hit or rebuild — is stored at its own input index.\n // Pushing hits onto a dense array while rebuilds are assigned by index\n // silently drops entries whenever a miss precedes a hit: the rebuild lands\n // on the slot the hit already occupied.\n const files: ReconcileManifestFile[] = new Array(allFiles.length);\n // Cache hits stay on a synchronous fast path (no promise allocation, no\n // Promise.all — a fully-cached rebuild must not regress); only the uncached\n // entries ride the bounded batches.\n const uncached: Array<{ index: number; file: ReconcileFileState }> = [];\n for (let i = 0; i < allFiles.length; i += 1) {\n const file = allFiles[i]!;\n const cached = cachedByPath.get(file.path);\n if (\n cached !== undefined &&\n cached.sha256.toLowerCase() === file.sha256.toLowerCase() &&\n isCachedIdentityReusable(cached)\n ) {\n files[i] = {\n ...file,\n ...(cached.memory\n ? { memory: cached.memory }\n : {\n normalizerVersion: cached.normalizerVersion,\n identityResolutionVersion: cached.identityResolutionVersion,\n }),\n };\n } else {\n uncached.push({ index: i, file });\n }\n }\n for (let i = 0; i < uncached.length; i += MANIFEST_BUILD_BATCH_SIZE) {\n // Between batches, not within one: an abort mid-batch lets the in-flight\n // reads settle (their own fetch signal does the aborting) and stops the\n // NEXT batch from ever being scheduled.\n options.signal?.throwIfAborted();\n const batch = uncached.slice(i, i + MANIFEST_BUILD_BATCH_SIZE);\n const results = await Promise.all(\n batch.map(({ file }) =>\n buildReconcileManifestFile(file, options.readFile, options.parseMemory, options.citationTemplate)\n )\n );\n for (let j = 0; j < batch.length; j += 1) {\n files[batch[j]!.index] = results[j]!;\n }\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 memoryIdentityHashes(memory: ReconcileMemoryIdentity): string[] {\n return [memory.contentHash, ...(memory.contentHashAliases ?? [])];\n}\n\nfunction contentHashRows(files: Iterable<ActiveFactManifestFile>, contentHash: string): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash });\n }\n return rows;\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) => [semanticAgreementKey(agreement), agreement])\n );\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n }\n for (const file of peerByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n const processedPathPairs = new Set<string>();\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 (\n opposite === undefined ||\n (activeOpposite !== undefined && memoryIdentityHashes(activeOpposite.memory).includes(hash))\n );\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 (\n opposite === undefined ||\n (activeOpposite !== undefined && memoryIdentityHashes(activeOpposite.memory).includes(hash))\n );\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates, hash));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates, hash));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const pathPair = `${localPath}\\0${peerPath}`;\n if (processedPathPairs.has(pathPair)) continue;\n processedPathPairs.add(pathPair);\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(\n 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 );\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(compareReconcilePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(compareReconcilePlanEntries);\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"]}
@@ -382,6 +382,8 @@ export {
382
382
  reconcileProvenanceRead,
383
383
  parseProvenanceSources,
384
384
  parseProvenanceConfig,
385
+ locateQuoteOffsets,
386
+ stripLeadingRolePrefix,
385
387
  buildFactProvenance
386
388
  };
387
- //# sourceMappingURL=chunk-RW4QNB4J.js.map
389
+ //# sourceMappingURL=chunk-S45INSV3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provenance.ts"],"sourcesContent":["/**\n * Claim-level provenance spans (issue #1575 PR 1).\n *\n * Centralizes the parse/serialize logic for the `sources` array and the\n * coarse `provenance` strength tag, plus the `provenance` config-block\n * parser. Extracted from `storage.ts` and `config.ts` so those files keep\n * only thin delegation call-sites — the frontmatter round-trip and config\n * parsing growth lives here (issue #1520 ratchet discipline).\n *\n * Contract:\n * - When no provenance fields are present, output is byte-identical to\n * pre-feature behavior (rule 39).\n * - Corrupt `sources` lines / unknown `provenance` tags drop to\n * `undefined` on read so a malformed frontmatter never poisons\n * downstream readers (rule 34 spirit — drop corrupt rather than poison).\n * - Validation lives on the write path; this module only parses.\n * - Invariant (review round 5, cursor thread KQN): a `verified` or\n * `unverified` tag is NEVER persisted/read without surviving sources —\n * without excerpts the tag is indistinguishable from a grounded fact to\n * downstream faithfulness/correction/TrustScore surfaces. The invariant\n * is enforced symmetrically: `serializeProvenanceFields` (write) and\n * `reconcileProvenanceRead` (read) both downgrade the tag to `none`\n * when no source survives.\n */\n\nimport { z } from \"zod\";\n\nimport { coerceBool, coerceNumber } from \"./connectors/coerce.js\";\nimport { readEnvVar } from \"./runtime/env.js\";\nimport { collapseWhitespace } from \"./whitespace.js\";\nimport type { MemoryFrontmatter, ProvenanceConfig, ProvenanceSource } from \"./types.js\";\nimport { isSafeMemoryContent } from \"./sanitize.js\";\n\n/**\n * Canonical key order for a serialized `ProvenanceSource` (issue #1575).\n * Deterministic emission (rule 38) — readers and the byte-identical-when-off\n * contract (rule 39) depend on this order never drifting.\n */\nconst PROVENANCE_SOURCE_KEY_ORDER = [\n \"sessionKey\",\n \"turnId\",\n \"observedAt\",\n \"quote\",\n \"charStart\",\n \"charEnd\",\n] as const;\n\n/**\n * Build a single `ProvenanceSource` object whose keys appear in the canonical\n * order, omitting absent optional fields. The result is what gets fed to\n * `JSON.stringify` so the on-disk line is deterministic.\n */\nfunction canonicalProvenanceSource(src: ProvenanceSource): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of PROVENANCE_SOURCE_KEY_ORDER) {\n const value = src[key];\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\n/**\n * Serialize the `sources` array (issue #1575) as a single JSON line, matching\n * the `structuredAttributes` precedent. Each entry is rebuilt in canonical key\n * order (rule 38) so the output is byte-stable. The `provenance` enum is\n * emitted bare (same style as `status`) — only the three documented values\n * are ever written.\n *\n * Verified-requires-evidence invariant (review round 5, cursor thread KQN):\n * a `verified`/`unverified` tag is downgraded to `none` whenever no source\n * survives write validation — whether sources were absent, an empty array,\n * or all entries failed the schema. This covers all three failure shapes the\n * earlier 3-branch logic left open (`{provenance:\"verified\"}`,\n * `{sources:[],provenance:\"verified\"}`, `{sources:[invalid…],provenance:\"verified\"}`).\n */\nexport function serializeProvenanceFields(fm: MemoryFrontmatter, lines: string[]): void {\n let hasValidSources = false;\n if (fm.sources && fm.sources.length > 0) {\n // Validate each entry against the same schema used on read so invalid\n // in-memory sources are dropped at write time, not silently lost on the\n // next read (review thread 4 — write-path validation parity).\n const canonical: Record<string, unknown>[] = [];\n for (const src of fm.sources) {\n const result = ProvenanceSourceSchema.safeParse(src);\n if (result.success) canonical.push(canonicalProvenanceSource(result.data));\n }\n if (canonical.length > 0) {\n lines.push(`sources: ${JSON.stringify(canonical)}`);\n hasValidSources = true;\n }\n }\n // A verified/unverified tag requires surviving evidence; without it the\n // tag is meaningless downstream (faithfulness/TrustScore cannot distinguish\n // it from a grounded fact). Downgrade to \"none\" regardless of WHY no source\n // survived (absent / empty / all-invalid) — single invariant, all cases.\n const tag =\n (fm.provenance === \"verified\" || fm.provenance === \"unverified\") && !hasValidSources\n ? \"none\"\n : fm.provenance;\n if (tag) {\n lines.push(`provenance: ${tag}`);\n }\n}\n\n/**\n * Parse the coarse `provenance` strength tag (issue #1575). Returns\n * `undefined` for missing/blank/unknown values so a corrupt or hand-edited\n * field fails safely to the legacy-equivalent `\"none\"` semantics on read\n * (rule 34 spirit — drop corrupt rather than poison).\n */\nexport function parseProvenanceTag(\n raw: string | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed === \"verified\" || trimmed === \"unverified\" || trimmed === \"none\") {\n return trimmed;\n }\n return undefined;\n}\n\n/**\n * Enforce the verified-requires-evidence invariant on the READ path.\n * `parseProvenanceTag` and `parseProvenanceSources` are independent (they\n * parse separate frontmatter lines), so a hand-edited or imported memory may\n * carry `provenance: verified` with no surviving `sources` — a corrupt line,\n * an empty array, or all-invalid entries. Downgrade such a tag to `none` so\n * the in-memory object never exposes an ungrounded \"verified\" fact\n * (review round 5, cursor thread KQN — read-path parity with the write-path\n * downgrade in `serializeProvenanceFields`). `none`/`undefined` tags pass\n * through unchanged.\n */\nexport function reconcileProvenanceRead(\n tag: \"verified\" | \"unverified\" | \"none\" | undefined,\n sources: ProvenanceSource[] | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if ((tag === \"verified\" || tag === \"unverified\") && (!sources || sources.length === 0)) {\n return \"none\";\n }\n return tag;\n}\n\n/**\n * Strict ISO-8601 timestamp check (review round 6, codex thread OXPAp).\n * `Date.parse` accepts non-ISO strings (bare years like `\"123\"`) and\n * silently normalizes calendar overflow (`2026-02-30` -> March 2, hour 25\n * -> next day), so malformed provenance survives as if valid. Require the\n * full `YYYY-MM-DDTHH:MM:SS[.fff](Z|±HH:MM)` shape and reject overflow via a\n * `Date.UTC` round-trip component check — the offset does not affect whether\n * a wall-clock field overflows, so this is correct for any timezone suffix.\n */\nfunction isStrictIsoTimestamp(s: string): boolean {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/.exec(s);\n if (!m) return false;\n const y = Number(m[1]), mo = Number(m[2]), da = Number(m[3]);\n const h = Number(m[4]), mi = Number(m[5]), se = Number(m[6]);\n // Date.UTC normalizes overflow (Feb 30 -> Mar 2); a component round-trip\n // catches what Date.parse silently accepts.\n const d = new Date(Date.UTC(y, mo - 1, da, h, mi, se));\n // The component round-trip validates wall-clock overflow (Feb 30 -> Mar 2);\n // Date.parse additionally rejects impossible timezone offsets such as\n // +99:99, which the regex accepts but the runtime treats as NaN\n // (codex thread OXQ0e).\n return (\n !Number.isNaN(Date.parse(s)) &&\n d.getUTCFullYear() === y &&\n d.getUTCMonth() === mo - 1 &&\n d.getUTCDate() === da &&\n d.getUTCHours() === h &&\n d.getUTCMinutes() === mi &&\n d.getUTCSeconds() === se\n );\n}\n\n/**\n * Coerce a turn timestamp to a strict ISO-8601 string (cursor thread Ocveu —\n * \"Non-ISO turn timestamps drop sources\"). The write-path `ProvenanceSourceSchema`\n * rejects anything `isStrictIsoTimestamp` fails, so a turn whose `timestamp`\n * parses via `new Date(...)` but isn't already strict ISO (e.g.\n * `\"2026/01/15 12:00:00\"` or `\"Jan 15 2026\"`) would be silently dropped at\n * serialization — clearing the whole `sources` array and downgrading the tag\n * to `\"none\"`. Normalizing at extraction time preserves the source.\n *\n * Returns the strict ISO string when the timestamp already passes or\n * `Date.parse` can round-trip it; `undefined` for empty / unparseable input\n * so callers can decide whether to skip the source or fall back.\n */\nfunction toStrictIsoTimestamp(ts: string | undefined | null): string | undefined {\n if (typeof ts !== \"string\" || ts.length === 0) return undefined;\n // Trim surrounding whitespace: Date.parse accepts leading/trailing\n // whitespace, so an imported \" 2026-05\" would otherwise bypass the\n // year-led partial-date guard below and fabricate May 1\n // (chatgpt-codex-connector review on #1714, issue #1657).\n const value = ts.trim();\n if (value.length === 0) return undefined;\n if (isStrictIsoTimestamp(value)) return value;\n const parsed = Date.parse(value);\n if (Number.isNaN(parsed)) return undefined;\n // Year-led date guard (issues #1657 + #1723). `parseYearLedDatePrefix`\n // recognizes a COMPLETE, calendar-valid year-led prefix — numeric OR textual\n // month — with real date-separator RUNS of non-alphanumeric, non-colon chars\n // (so complete-but-unusual-punctuation dates like \"2026 May, 10\",\n // \"2026, May 10\", \"2026 Jan. 15\" are accepted), the day-not-the-hour\n // invariant via the `(?![\\d:])` lookahead, and a calendar-valid day. This\n // single rule subsumes the prior partial-numeric (\"2026-05\"), textual-partial\n // (\"2026-Jan\"), and calendar-overflow (\"2026-02-30\", \"2026-Feb-30\") checks\n // and extends them to textual months — Date.parse silently fills missing\n // components and rolls overflow, fabricating observedAt, so a non-complete\n // or invalid year-led prefix is rejected. Non-year-led shapes\n // (\"Jan 15 2026\", \"02/30/2026\") fall through to Date.parse / the mdy check.\n const yearLedPrefix = parseYearLedDatePrefix(value);\n // Agreement check (issue #1723 codex r2/r3/r4): exotic punctuation can make\n // the prefix structure disagree with Date.parse's own interpretation — e.g.\n // \"2026 (May) 10\" parses as a valid May 10 PREFIX but Date.parse reads\n // October 1; \"2026 May (10)\" agrees on month but Date.parse reads day 1 — so\n // the round-trip would persist a fabricated observedAt while the guard\n // \"validated\" a different date. Require Date.parse's interpretation to match\n // the prefix's FULL (y, m, d) tuple. Offset-bearing inputs compare in the\n // source's explicit offset frame (the prefix date is offset-local, so a UTC\n // instant legitimately falls on an adjacent UTC/local day); offset-free\n // inputs compare both the local and UTC frames (Date.parse uses UTC for ISO\n // date-only strings and local for non-ISO — a single frame would wrongly\n // reject legitimate month/day-boundary dates that shift across timezones).\n const yearLedAgrees = yearLedAgreesWithDateParse(parsed, value, yearLedPrefix);\n const validYearLed = yearLedPrefix !== null && yearLedAgrees;\n // Reject bare-year / numeric-only strings that Date.parse accepts (e.g.\n // \"123\") — isStrictIsoTimestamp already rejected them, and the round-trip\n // below would otherwise resurrect them. Require at least one date/time\n // separator so a plain number never round-trips into a fake epoch — UNLESS\n // the value is itself a complete, calendar-valid year-led date that\n // Date.parse agrees with (issue #1723: a date-only punctuated textual form\n // like \"2026 May, 10\" has no -/:/T yet parses to the correct calendar day).\n if (!/[-:T]/.test(value) && !validYearLed) {\n return undefined;\n }\n // Reject year-led timestamps that are not a COMPLETE, calendar-valid date\n // Date.parse agrees with (covers partial, overflow, AND the punctuation\n // misinterpretation above).\n if (/^\\d{4}\\D/.test(value) && !validYearLed) {\n return undefined;\n }\n // Also validate M/D/Y or D/M/Y formats (provider/import common shapes:\n // 02/30/2026, 30-02-2026, etc.). Date.parse silently shifts overflow in\n // these too. Try both month-first and day-first interpretations; if\n // neither is a valid calendar date, reject (chatgpt-codex-connector thread\n // dH47 — non-YMD overflow timestamps).\n const mdy = /^(\\d{1,2})\\D(\\d{1,2})\\D(\\d{4})/.exec(value);\n if (mdy) {\n const a = Number(mdy[1]), b = Number(mdy[2]), yr = Number(mdy[3]);\n if (!isValidCalendarDate(yr, a, b) && !isValidCalendarDate(yr, b, a)) {\n return undefined;\n }\n }\n const iso = new Date(parsed).toISOString();\n return isStrictIsoTimestamp(iso) ? iso : undefined;\n}\n\n/**\n * Validate numeric calendar components without Date overflow (review thread\n * dANc). Date.UTC silently normalizes Feb 30 -> Mar 2; a component round-trip\n * catches what Date.parse accepts. Mirrors the same technique isStrictIsoTimestamp\n * uses for full ISO strings, applied here to the non-ISO normalization path.\n */\nfunction isValidCalendarDate(y: number, mo: number, da: number): boolean {\n if (mo < 1 || mo > 12 || da < 1) return false;\n const leap = (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)) ? 29 : 28;\n const daysInMonth = [31, leap, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n return da <= daysInMonth[mo - 1]!;\n}\n\n/**\n * Recognized month-name → number map for year-led textual-month dates\n * (issue #1657). Both the 3-letter abbreviations and the full names that\n * Date.parse accepts for year-first shapes (e.g. \"2026-Jan-15\",\n * \"2026-January-15\", \"2026 February 2\").\n */\nconst MONTH_NAMES: Record<string, number> = {\n jan: 1, january: 1,\n feb: 2, february: 2,\n mar: 3, march: 3,\n apr: 4, april: 4,\n may: 5,\n jun: 6, june: 6,\n jul: 7, july: 7,\n aug: 8, august: 8,\n sep: 9, september: 9,\n sept: 9,\n oct: 10, october: 10,\n nov: 11, november: 11,\n dec: 12, december: 12,\n};\n\n/**\n * Parse a complete, calendar-valid year-led date prefix from `s`\n * (issue #1657, relaxed in #1723): `YYYY <sep> (MM | MonthName) <sep> DD`\n * where each separator is a RUN of non-alphanumeric, non-colon chars (so a\n * complete date with any punctuation — \"2026 May, 10\", \"2026, May 10\",\n * \"2026 Jan. 15\" — is accepted; the run excludes letters so a textual month\n * is never consumed by the separator, and excludes the colon so the date/time\n * boundary stays clean) and the day is a true calendar day — not the hour\n * before a `:` time separator (the `(?![\\d:])` lookahead rejects\n * \"2026-05 10:00\" capturing \"10\" as the day). Returns the numeric components\n * for a complete, valid date; `null` for an incomplete (\"2026-05\",\n * \"2026-Jan\"), overflowed (\"2026-02-30\", \"2026-Feb-30\"), or non-year-led\n * prefix. Centralizes the fabricated-date rejection for the\n * toStrictIsoTimestamp normalization path across numeric and textual months\n * in one rule.\n */\nfunction parseYearLedDatePrefix(s: string): { y: number; mo: number; da: number } | null {\n const m = /^(\\d{4})[^0-9A-Za-z]+(\\d{1,2}|[A-Za-z]{3,})[^0-9A-Za-z:]+(\\d{1,2})(?![\\d:])/.exec(s);\n if (!m) return null;\n const y = Number(m[1]);\n const da = Number(m[3]);\n const mo = /^\\d+$/.test(m[2]) ? Number(m[2]) : (MONTH_NAMES[m[2]!.toLowerCase()] ?? -1);\n if (!isValidCalendarDate(y, mo, da)) return null;\n return { y, mo, da };\n}\n\n/**\n * Extract an explicit timezone offset (ms east of UTC) from a non-strict-ISO\n * timestamp, or `null` if none is present (issue #1723 codex r4). Recognizes\n * the offset forms `Date.parse` accepts for non-ISO year-led dates: a trailing\n * `±HH:MM` / `±HHMM`, and a `GMT`/`UTC` token optionally followed by\n * `±HHMM` (bare `GMT`/`UTC` ⇒ offset 0). Strict ISO `Z` forms are handled\n * by `isStrictIsoTimestamp` before this point, so they never reach here.\n */\n/**\n * Named timezone abbreviations Node's `Date.parse` accepts for non-ISO dates\n * (issue #1723 codex r8). V8 recognizes a fixed set of US zones; each maps to a\n * fixed offset (the abbreviation itself encodes standard vs daylight, so PST is\n * always -8 and PDT always -7 regardless of the calendar date). Other named\n * zones (CET/CEST/JST/…) return NaN from `Date.parse` and never reach here.\n */\nconst NAMED_ZONE_OFFSETS_MS: Record<string, number> = {\n PST: -8 * 60, PDT: -7 * 60,\n MST: -7 * 60, MDT: -6 * 60,\n CST: -6 * 60, CDT: -5 * 60,\n EST: -5 * 60, EDT: -4 * 60,\n};\n\nfunction explicitOffsetMs(value: string): number | null {\n // Recognize the offset forms Date.parse accepts for non-ISO year-led dates,\n // returning ms east of UTC or null. Tries each shape in order; a stray run\n // of date digits can never pose as an offset because (a) trailing numeric\n // offsets must be preceded by whitespace or glued to a \":SS\" seconds field\n // (lookbehind), and (b) every candidate is range-validated (|hh| ≤ 14,\n // mm < 59).\n // Named US zone abbreviation (PST/EST/EDT/…), trailing token (codex r8).\n const nz = /\\b([A-Z]{3,4})\\s*$/.exec(value);\n if (nz && NAMED_ZONE_OFFSETS_MS[nz[1]] !== undefined) {\n return NAMED_ZONE_OFFSETS_MS[nz[1]] * 60_000;\n }\n const tryOff = (sign: string | undefined, hh: number, mm: number): number | null => {\n if (hh > 14 || mm > 59) return null;\n return (sign === \"-\" ? -1 : 1) * (hh * 60 + mm) * 60_000;\n };\n let m: RegExpExecArray | null;\n // GMT/UTC token: colon form, then ±HHMM (4-digit, before bare hour so\n // \"GMT+0530\" is not read as \"+05\"), then bare hour, then the bare token\n // itself (offset 0).\n if (\n (m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{1,2}):(\\d{2})/i.exec(value)) ||\n (m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{2})(\\d{2})/i.exec(value))\n ) {\n const r = tryOff(m[1], Number(m[2]), Number(m[3]));\n if (r !== null) return r;\n }\n if ((m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{1,2})\\b/i.exec(value))) {\n const r = tryOff(m[1], Number(m[2]), 0);\n if (r !== null) return r;\n }\n if (/\\b(?:GMT|UTC)\\b/i.test(value)) return 0;\n // Trailing numeric offset: whitespace-delimited OR glued to a \":SS\"\n // seconds field (lookbehind), so date digits (\"-0510\" in \"2026-0510\") are\n // never misread as an offset. Colon form, then ±HHMM, then bare hour — each\n // for BOTH the whitespace and glued contexts.\n if (\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{1,2}):(\\d{2})\\s*$/.exec(value)) ||\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{2})(\\d{2})\\s*$/.exec(value)) ||\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{1,2})\\s*$/.exec(value))\n ) {\n const r = tryOff(m[1], Number(m[2]), m[3] ? Number(m[3]) : 0);\n if (r !== null) return r;\n }\n return null;\n}\n\n/**\n * Verify `Date.parse`'s interpretation of `value` agrees with the year-led\n * `prefix` components `(y, m, d)` (issue #1723 codex r2/r3/r4). Returns\n * `false` when `prefix` is `null` (no complete year-led date). Otherwise\n * compares the FULL tuple so punctuation that shifts the month OR the day is\n * caught. Offset-bearing inputs are compared in the source's explicit offset\n * frame (the prefix date is offset-local, so the same instant legitimately\n * lands on an adjacent UTC/local day); offset-free inputs accept either the\n * local or UTC frame, because `Date.parse` uses UTC for ISO date-only strings\n * and local for non-ISO forms.\n */\nfunction yearLedAgreesWithDateParse(\n parsed: number,\n value: string,\n prefix: { y: number; mo: number; da: number } | null,\n): boolean {\n if (prefix === null) return false;\n const off = explicitOffsetMs(value);\n if (off !== null) {\n const shifted = new Date(parsed + off);\n return (\n shifted.getUTCFullYear() === prefix.y &&\n shifted.getUTCMonth() + 1 === prefix.mo &&\n shifted.getUTCDate() === prefix.da\n );\n }\n const pd = new Date(parsed);\n const localMatch =\n pd.getFullYear() === prefix.y &&\n pd.getMonth() + 1 === prefix.mo &&\n pd.getDate() === prefix.da;\n const utcMatch =\n pd.getUTCFullYear() === prefix.y &&\n pd.getUTCMonth() + 1 === prefix.mo &&\n pd.getUTCDate() === prefix.da;\n return localMatch || utcMatch;\n}\n\n/**\n * Zod schema for a single `ProvenanceSource` entry (issue #1575). Parsed\n * JSON from frontmatter is external data, so each entry is validated here\n * rather than trusted via a cast (rule: no inline-cast-access on parsed\n * blobs). `safeParse` lets us drop corrupt entries individually instead of\n * failing the whole field.\n */\nconst ProvenanceSourceSchema = z\n .object({\n sessionKey: z.string().min(1),\n observedAt: z\n .string()\n .min(1)\n .refine(isStrictIsoTimestamp, \"must be a valid ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SS[Z|±HH:MM], no calendar overflow)\"),\n quote: z.string().min(1),\n turnId: z.string().min(1).optional(),\n charStart: z.number().finite().nonnegative().int().optional(),\n charEnd: z.number().finite().nonnegative().int().optional(),\n })\n .refine(\n (src) => src.charStart === undefined || src.charEnd === undefined || src.charEnd >= src.charStart,\n { message: \"charEnd must be >= charStart (half-open interval, rule 35)\" },\n );\n\n/**\n * Parse the `sources` array (issue #1575) from its single-line JSON form.\n * Mirrors `parseStructuredAttributes` (JSON.parse) but validates each entry\n * against `ProvenanceSourceSchema` and DROPS corrupt ones rather than\n * poisoning downstream readers — the same \"drop corrupt rather than poison\"\n * contract as `parseMemoryWorthCounterField`. If no entry survives, the\n * whole field is `undefined` (legacy-equivalent).\n */\nexport function parseProvenanceSources(raw: string | undefined): ProvenanceSource[] | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n if (!Array.isArray(parsed)) return undefined;\n const sources: ProvenanceSource[] = [];\n for (const entry of parsed) {\n const result = ProvenanceSourceSchema.safeParse(entry);\n if (result.success) sources.push(result.data);\n }\n return sources.length > 0 ? sources : undefined;\n}\n\n/**\n * Parse the `provenance` config block (issue #1575 PR 1). Validates the\n * shape before applying defaults — a shorthand like `provenance: false` must\n * reject loudly rather than normalize to `{}` and silently enable the feature\n * (rule 51). Booleans coerce via `coerceBool` (rule 36); numeric cap clamps\n * at 1 (rule 28). `REMNIC_PROVENANCE_ENABLED` / `ENGRAM_PROVENANCE_ENABLED`\n * are honored only when the `enabled` key is omitted (explicit config wins).\n *\n * Schema-default note (review rounds 1–3, settled): `provenance.enabled` has\n * NO `\"default\"` in any plugin.json schema. OpenClaw's loader runs\n * `applyDefaults: true` before exposing `api.pluginConfig` (src/index.ts:1345,\n * PR #1593 round 8), so a schema default would be materialized into the\n * merged config and mask the `REMNIC_`/`ENGRAM_` env override. The code-level\n * default-on here (`return true` when `enabled` is omitted) supplies the\n * default-on behavior without that materialization. This matches the\n * emitLegacyTools/namespaceCatalogEnabled precedent, which omits the env\n * override only after a raw-vs-effective split — overkill for a single\n * boolean, so this field uses the simpler omit-the-default approach.\n */\nexport function parseProvenanceConfig(raw: unknown): ProvenanceConfig {\n if (\n raw !== undefined &&\n (raw === null || typeof raw !== \"object\" || Array.isArray(raw))\n ) {\n throw new Error(\n `provenance must be an object (got ${JSON.stringify(raw)}). Use provenance: { enabled: false } to opt out; omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n const rawProvenance =\n raw && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n return {\n enabled: (() => {\n if (rawProvenance.enabled === undefined) {\n const envEnabled =\n readEnvVar(\"REMNIC_PROVENANCE_ENABLED\") ?? readEnvVar(\"ENGRAM_PROVENANCE_ENABLED\");\n if (envEnabled !== undefined) {\n const coerced = coerceBool(envEnabled);\n if (coerced === undefined) {\n throw new Error(\n `REMNIC_PROVENANCE_ENABLED must be a boolean-like value (true/false/1/0/yes/no/on/off); got ${JSON.stringify(envEnabled)}`,\n );\n }\n return coerced;\n }\n return true;\n }\n const coerced = coerceBool(rawProvenance.enabled);\n if (coerced === undefined) {\n throw new Error(\n `provenance.enabled must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.enabled)}). Omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n return coerced;\n })(),\n maxQuoteChars: (() => {\n if (rawProvenance.maxQuoteChars === undefined) return 300;\n const rawCap = coerceNumber(rawProvenance.maxQuoteChars);\n // Reject present-but-invalid rather than silently widening the cap\n // (AGENTS.md input-validation rule — a typo should not persist more\n // text than the operator configured).\n if (rawCap === undefined || !Number.isFinite(rawCap) || rawCap < 1 || !Number.isInteger(rawCap)) {\n throw new Error(\n `provenance.maxQuoteChars must be a positive integer >= 1 (got ${JSON.stringify(rawProvenance.maxQuoteChars)}).`,\n );\n }\n return rawCap;\n })(),\n requireSpans: (() => {\n if (rawProvenance.requireSpans === undefined) return false;\n const coerced = coerceBool(rawProvenance.requireSpans);\n if (coerced === undefined) {\n throw new Error(\n `provenance.requireSpans must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.requireSpans)}).`,\n );\n }\n return coerced;\n })(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1575 PR 2 — extraction-side post-parse validator.\n// ---------------------------------------------------------------------------\n//\n// This runs once at write time (inside `ExtractionEngine.extract`, after the\n// LLM output is parsed and sanitized, before the result is returned for\n// persistence). Its job: locate each fact's LLM-provided `quote` in the\n// buffered turn texts and build a `ProvenanceSource[]` with verified\n// offsets. Never throws, never drops a fact (rule 34 spirit — an\n// unverifiable span is a tagged state, not a silent failure).\n//\n// Reuses `collapseWhitespace` from `whitespace.ts` for normalization so there\n// is exactly one normalizer in the codebase (issue #1575 pitfall: \"do not\n// write a second normalizer\").\n// ---------------------------------------------------------------------------\n\n/**\n * A turn in the buffered conversation, reduced to the fields the provenance\n * validator needs. `ExtractionEngine.extract` maps its `BufferTurn[]` to this\n * shape so the validator is pure and testable without the full BufferTurn type.\n */\nexport interface ProvenanceTurnInput {\n content: string;\n sessionKey?: string;\n logicalSessionKey?: string;\n timestamp: string;\n turnId?: string;\n}\n\n/**\n * Result of building provenance for a single extracted fact.\n */\nexport interface ProvenanceBuildResult {\n /** Verified sources (one per matching turn). Absent when no quote survives. */\n sources?: ProvenanceSource[];\n /** Coarse strength tag persisted to frontmatter. */\n provenance: \"verified\" | \"unverified\" | \"none\";\n /**\n * Transient signal (never persisted): `true` when `config.requireSpans`\n * is enabled and the LLM-provided quote could not be located in ANY source\n * turn (the strict case `ProvenanceConfig.requireSpans` documents: \"facts\n * whose quote cannot be located are routed to `pending_review`\"). The\n * extraction consumer carries this onto the in-memory `ExtractedFact` so\n * the persist path can route the fact to the review queue. A quote that\n * WAS located but whose source was dropped (e.g. un-coercible timestamp)\n * does NOT set this flag — the span was found, so requireSpans is\n * satisfied (chatgpt-codex-connector thread 4xB).\n */\n requireSpansPending?: boolean;\n}\n\n/**\n * Cap a quote string at `maxChars`, truncating at the last word boundary that\n * fits and appending an ellipsis marker. Quotes at or under the cap pass\n * through unchanged. Operates on Unicode code points (not UTF-16 units) so\n * emoji and astral-plane characters are not split mid-glyph.\n */\nfunction capQuote(quote: string, maxChars: number): string {\n const glyphs = Array.from(quote);\n if (glyphs.length <= maxChars) return quote;\n // Walk backward from the cap to find a word boundary (space). If the entire\n // span is one long token (no spaces), cut at the cap — a hard cut is better\n // than no quote at all.\n let cut = maxChars;\n while (cut > 0 && !/\\s/.test(glyphs[cut - 1]!)) cut--;\n if (cut === 0) cut = maxChars; // no word boundary found\n return glyphs.slice(0, cut).join(\"\").trimEnd() + \"\\u2026\";\n}\n\n/**\n * Casefold a string for normalized matching. Uses `toLowerCase` (not\n * `toLocaleLowerCase`) for determinism across runtime locales — the existing\n * `normalizeFactKey` helper in extraction.ts follows the same convention.\n */\nfunction casefold(s: string): string {\n return s.toLowerCase();\n}\n\n/**\n * Locate `quote` within `text`, returning whether a match was found and, when\n * recoverable, the half-open `[start, end)` offsets. Tries exact substring\n * first, then whitespace/case-normalized match.\n *\n * For the normalized path, the mapping from the normalized string back to\n * original offsets is recovered by walking both strings in lockstep: we know\n * `collapseWhitespace(text)` is a subsequence of `text` with whitespace runs\n * collapsed, so we can track the original offset as we scan.\n *\n * Returns `{ matched: false }` when neither match succeeds. Returns\n * `{ matched: true, offsets }` when offsets are recoverable. Returns\n * `{ matched: true }` (offsets omitted) when the normalized substring was\n * found but original offsets could not be recovered — callers still treat\n * this as a verified match and record a source without offsets (cursor\n * thread Ocver — \"Normalized match drops verified provenance\").\n */\ntype LocateQuoteResult =\n | { matched: false }\n | { matched: true; offsets?: { charStart: number; charEnd: number } };\n\nfunction locateQuoteOffsets(quote: string, text: string): LocateQuoteResult {\n // 1. Exact substring match (handles unicode, curly quotes, emoji verbatim).\n const exactIdx = text.indexOf(quote);\n if (exactIdx >= 0) {\n return { matched: true, offsets: { charStart: exactIdx, charEnd: exactIdx + quote.length } };\n }\n\n // 2. Whitespace/case-normalized match. Collapse runs of whitespace and\n // casefold both sides, then find the normalized quote in the normalized\n // text. Recover original offsets by scanning forward from the normalized\n // match start through the original text, accumulating non-whitespace\n // glyphs until we've consumed the normalized quote length.\n const normQuote = collapseWhitespace(casefold(quote));\n if (normQuote.length === 0) return { matched: false };\n const normText = collapseWhitespace(casefold(text));\n const normIdx = normText.indexOf(normQuote);\n if (normIdx < 0) return { matched: false };\n\n // Recover original offsets: walk the original text, skipping leading\n // whitespace to align with the collapsed form, then track how many\n // normalized chars we've consumed.\n const normQuoteLen = normQuote.length;\n let origIdx = 0;\n // Skip leading whitespace in original text (collapseWhitespace trims both ends).\n while (origIdx < text.length && /\\s/.test(text[origIdx]!)) origIdx++;\n // Walk through original text, counting normalized chars consumed.\n // Each non-whitespace char in original = 1 normalized char.\n // Whitespace runs in original = 1 space in normalized (but only between non-ws).\n let normPos = 0;\n let origStart = -1;\n let origEnd = -1;\n let prevWasWs = true; // suppress the leading space in collapsed form\n while (origIdx < text.length && origEnd < 0) {\n const ch = text[origIdx]!;\n if (/\\s/.test(ch)) {\n if (!prevWasWs) {\n // This whitespace run collapses to a single space in normalized text.\n if (normPos === normIdx) origStart = origIdx; // normalized space aligns\n normPos++;\n prevWasWs = true;\n }\n origIdx++;\n continue;\n }\n // Non-whitespace char.\n if (normPos === normIdx && origStart < 0) {\n origStart = origIdx;\n }\n normPos++;\n prevWasWs = false;\n origIdx++;\n if (normPos >= normIdx + normQuoteLen) {\n origEnd = origIdx;\n }\n }\n if (origStart >= 0 && origEnd >= 0) {\n return { matched: true, offsets: { charStart: origStart, charEnd: origEnd } };\n }\n // Normalized substring was found, but original offsets are not recoverable\n // (edge case in the normalized mapping). The match still counts as verified\n // — record a source without offsets rather than dropping the turn entirely\n // (cursor thread Ocver). charStart/charEnd are best-effort debugging aids,\n // not a precondition for a verified source.\n return { matched: true };\n}\n\n/**\n * Build provenance sources for a single extracted fact by locating its\n * LLM-provided `quote` in the buffered turns (issue #1575 PR 2).\n *\n * Matching strategy (per issue design):\n * 1. Exact substring match in a turn → `provenance: \"verified\"` with\n * `charStart`/`charEnd` offsets.\n * 2. Whitespace/case-normalized match → `\"verified\"`, offsets recovered\n * when possible, else omitted.\n * 3. No match in any turn → `\"unverified\"` — the quote survives as a\n * source (the LLM vouched for it) but without located offsets.\n * 4. No quote provided by the LLM → `\"none\"` — no evidence to record.\n *\n * A quote appearing in multiple turns produces multiple sources (one per\n * matching turn) so a repeated utterance backs the fact from each occurrence.\n *\n * The quote is capped at `config.maxQuoteChars` (truncated at a word boundary\n * with an ellipsis marker) BEFORE storage. Locating uses the original\n * (untruncated) quote for maximum match fidelity; the capped excerpt is what\n * gets persisted.\n *\n * When `config.enabled === false`, returns `{ provenance: \"none\" }`\n * immediately — byte-identical to pre-feature extraction (rule 39).\n *\n * Never throws. An unexpected error degrades to `{ provenance: \"none\" }` so\n * extraction never crashes on a provenance hiccup (rule 13/18).\n */\n/**\n * Strip a leading extraction-prompt role label from a quote (cursor thread Oc3Z2\n * — \"Quote prompt mismatches validator\"). The extraction prompt renders each\n * buffered turn as `[role] content` (or `[context role] content`), so a\n * faithful LLM may include that prefix in its verbatim quote. buildFactProvenance\n * searches the raw `turn.content` (no prefix), so a quote carrying the label\n * would never match. Stripping the leading label lets the actual utterance\n * verify against the turn text. Only a SINGLE leading label is stripped — a\n * multi-turn quote (with embedded labels) is left untouched and will simply\n * fail to match a single turn, as before.\n *\n * The regex is constrained to the labels the prompt actually emits — `user`,\n * `assistant`, optionally prefixed with `context ` (extraction.ts renders\n * `[user]`, `[assistant]`, `[context user]`, `[context assistant]`). A\n * real utterance that happens to start with other bracketed text (e.g.\n * `[do not] deploy before approval`, `[P1] fix the cache`) is preserved\n * verbatim so the quote can match the turn and the persisted span retains its\n * original meaning (chatgpt-codex-connector thread 4xA — limiting role-prefix\n * stripping to actual prompt labels).\n */\nfunction stripLeadingRolePrefix(quote: string): string {\n return quote.replace(/^\\s*\\[(?:context\\s+)?(?:user|assistant)\\]\\s+/i, \"\");\n}\n\nexport function buildFactProvenance(\n factQuote: string | null | undefined,\n turns: ReadonlyArray<ProvenanceTurnInput>,\n config: ProvenanceConfig,\n): ProvenanceBuildResult {\n if (!config.enabled) return { provenance: \"none\" };\n const rawQuote = typeof factQuote === \"string\" ? factQuote.trim() : \"\";\n // requireSpans (chatgpt-codex-connector thread dEsu + cursor thread dGKJ):\n // every early exit that drops a fact's span (no quote, empty after label\n // strip, or unsafe quote) must flag requireSpansPending when the operator\n // opted into requireSpans, so the persist path routes the fact to\n // pending_review instead of active. Only the disabled-feature exit (above)\n // and the unexpected-error catch (below) omit the flag — those are\n // policy-neutral degradations, not a missing-span decision.\n const noneResult = (): ProvenanceBuildResult =>\n config.requireSpans === true\n ? { provenance: \"none\", requireSpansPending: true }\n : { provenance: \"none\" };\n if (rawQuote.length === 0) return noneResult();\n // Strip a leading prompt role label so a faithful quote verifies against\n // the raw turn content (cursor thread Oc3Z2). Prefer the RAW quote when it\n // matches at least one turn — an utterance that literally begins with\n // [user]/[assistant] must verify as-is, not be truncated to the post-label\n // text (cursor/codex thread dEsw). The strip handles the common case where\n // the LLM includes the prompt label; this preserves the rare case where the\n // utterance itself starts with that text.\n const strippedQuote = stripLeadingRolePrefix(rawQuote);\n const useRawQuote =\n rawQuote === strippedQuote ||\n turns.some(\n (t) =>\n typeof t?.content === \"string\" &&\n t.content.length > 0 &&\n locateQuoteOffsets(rawQuote, t.content).matched,\n );\n const quote = useRawQuote ? rawQuote : strippedQuote;\n if (quote.length === 0) return noneResult();\n // Sanitize the quote before persisting it as a provenance span\n // (chatgpt-codex-connector thread dANZ): the fact body is sanitized via\n // sanitizeMemoryContent, but sources[].quote was persisted verbatim,\n // reintroducing unsafe memory text (e.g. \"ignore previous instructions\")\n // through the provenance field exposed to memory_get/x-ray/faithfulness.\n // An unsafe quote cannot serve as evidence — drop the source entirely\n // (consistent with how the body is sanitized: unsafe text is redacted,\n // and a redacted quote is useless as a verbatim span).\n if (!isSafeMemoryContent(quote)) return noneResult();\n\n try {\n // Search every turn for the quote. Collect verified sources.\n const sources: ProvenanceSource[] = [];\n // Track the first turn where the quote was located even when its\n // timestamp can't be coerced (cursor thread 4Pj — \"Bad timestamp drops\n // matched source\"). Without this, a located-but-unverifiable quote falls\n // through to the unverified branch and is attributed to the *last* turn's\n // session, mislabeling the source's origin session. Also drives the\n // requireSpans signal: only a quote that was NOT located in any turn\n // qualifies for pending_review routing under requireSpans (thread 4xB).\n let locatedTurn: ProvenanceTurnInput | undefined;\n for (const turn of turns) {\n if (!turn || typeof turn.content !== \"string\" || turn.content.length === 0) continue;\n const located = locateQuoteOffsets(quote, turn.content);\n // cursor thread Ocver: a normalized match counts as verified even when\n // original offsets are unrecoverable — record the source without\n // charStart/charEnd instead of skipping the turn.\n if (!located.matched) continue;\n if (!locatedTurn) locatedTurn = turn;\n // cursor thread Ocveu: normalize the turn timestamp to strict ISO so\n // the write-path ProvenanceSourceSchema keeps the source. Skip the turn\n // when the timestamp can't be coerced — pushing it would guarantee a\n // serialization drop (and the tag downgrade) for this source.\n const observedAt = toStrictIsoTimestamp(turn.timestamp);\n if (!observedAt) continue;\n sources.push({\n sessionKey: turn.sessionKey ?? turn.logicalSessionKey ?? \"unknown\",\n ...(turn.turnId ? { turnId: turn.turnId } : {}),\n observedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n ...(located.offsets\n ? { charStart: located.offsets.charStart, charEnd: located.offsets.charEnd }\n : {}),\n });\n }\n\n if (sources.length > 0) {\n return { sources, provenance: \"verified\" };\n }\n\n // Quote provided but could not be turned into a verified source.\n // Determine the session to attribute: prefer the turn where the quote was\n // LOCATED even if its timestamp couldn't be coerced (cursor thread 4Pj — a\n // located quote must not be tied to the *last* turn's session). If the\n // quote was not located in any turn at all, fall back to the last turn's\n // session (the documented unverified behavior: the LLM vouched for the\n // excerpt but we couldn't pin it to a character offset). Normalize the\n // timestamp (thread Ocveu); fall back to epoch when no turn supplies a\n // coercible timestamp so the unverified source still survives the\n // write-path schema.\n const fallbackTurn = locatedTurn ?? turns[turns.length - 1];\n const fallbackSessionKey = fallbackTurn\n ? (fallbackTurn.sessionKey ?? fallbackTurn.logicalSessionKey ?? \"unknown\")\n : \"unknown\";\n const fallbackObservedAt = fallbackTurn\n ? (toStrictIsoTimestamp(fallbackTurn.timestamp) ?? new Date(0).toISOString())\n : new Date(0).toISOString();\n // requireSpans signal (chatgpt-codex-connector thread 4xB): when an\n // operator opts into provenance.requireSpans, a fact whose quote could\n // not be located in ANY turn (locatedTurn undefined) is flagged so the\n // persist path routes it to pending_review instead of active. A quote\n // that WAS located (locatedTurn set) satisfies requireSpans even when\n // its source was dropped for an un-coercible timestamp — the span was\n // found, so the fact has the grounding requireSpans demands.\n const requireSpansPending =\n config.requireSpans === true && locatedTurn === undefined;\n return {\n sources: [\n {\n sessionKey: fallbackSessionKey,\n observedAt: fallbackObservedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n },\n ],\n provenance: \"unverified\",\n ...(requireSpansPending ? { requireSpansPending: true } : {}),\n };\n } catch {\n // Never crash extraction on a provenance error (rule 13/18).\n return { provenance: \"none\" };\n }\n}\n"],"mappings":"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","names":["coerced"]}
1
+ {"version":3,"sources":["../src/provenance.ts"],"sourcesContent":["/**\n * Claim-level provenance spans (issue #1575 PR 1).\n *\n * Centralizes the parse/serialize logic for the `sources` array and the\n * coarse `provenance` strength tag, plus the `provenance` config-block\n * parser. Extracted from `storage.ts` and `config.ts` so those files keep\n * only thin delegation call-sites — the frontmatter round-trip and config\n * parsing growth lives here (issue #1520 ratchet discipline).\n *\n * Contract:\n * - When no provenance fields are present, output is byte-identical to\n * pre-feature behavior (rule 39).\n * - Corrupt `sources` lines / unknown `provenance` tags drop to\n * `undefined` on read so a malformed frontmatter never poisons\n * downstream readers (rule 34 spirit — drop corrupt rather than poison).\n * - Validation lives on the write path; this module only parses.\n * - Invariant (review round 5, cursor thread KQN): a `verified` or\n * `unverified` tag is NEVER persisted/read without surviving sources —\n * without excerpts the tag is indistinguishable from a grounded fact to\n * downstream faithfulness/correction/TrustScore surfaces. The invariant\n * is enforced symmetrically: `serializeProvenanceFields` (write) and\n * `reconcileProvenanceRead` (read) both downgrade the tag to `none`\n * when no source survives.\n */\n\nimport { z } from \"zod\";\n\nimport { coerceBool, coerceNumber } from \"./connectors/coerce.js\";\nimport { readEnvVar } from \"./runtime/env.js\";\nimport { collapseWhitespace } from \"./whitespace.js\";\nimport type { MemoryFrontmatter, ProvenanceConfig, ProvenanceSource } from \"./types.js\";\nimport { isSafeMemoryContent } from \"./sanitize.js\";\n\n/**\n * Canonical key order for a serialized `ProvenanceSource` (issue #1575).\n * Deterministic emission (rule 38) — readers and the byte-identical-when-off\n * contract (rule 39) depend on this order never drifting.\n */\nconst PROVENANCE_SOURCE_KEY_ORDER = [\n \"sessionKey\",\n \"turnId\",\n \"observedAt\",\n \"quote\",\n \"charStart\",\n \"charEnd\",\n] as const;\n\n/**\n * Build a single `ProvenanceSource` object whose keys appear in the canonical\n * order, omitting absent optional fields. The result is what gets fed to\n * `JSON.stringify` so the on-disk line is deterministic.\n */\nfunction canonicalProvenanceSource(src: ProvenanceSource): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of PROVENANCE_SOURCE_KEY_ORDER) {\n const value = src[key];\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\n/**\n * Serialize the `sources` array (issue #1575) as a single JSON line, matching\n * the `structuredAttributes` precedent. Each entry is rebuilt in canonical key\n * order (rule 38) so the output is byte-stable. The `provenance` enum is\n * emitted bare (same style as `status`) — only the three documented values\n * are ever written.\n *\n * Verified-requires-evidence invariant (review round 5, cursor thread KQN):\n * a `verified`/`unverified` tag is downgraded to `none` whenever no source\n * survives write validation — whether sources were absent, an empty array,\n * or all entries failed the schema. This covers all three failure shapes the\n * earlier 3-branch logic left open (`{provenance:\"verified\"}`,\n * `{sources:[],provenance:\"verified\"}`, `{sources:[invalid…],provenance:\"verified\"}`).\n */\nexport function serializeProvenanceFields(fm: MemoryFrontmatter, lines: string[]): void {\n let hasValidSources = false;\n if (fm.sources && fm.sources.length > 0) {\n // Validate each entry against the same schema used on read so invalid\n // in-memory sources are dropped at write time, not silently lost on the\n // next read (review thread 4 — write-path validation parity).\n const canonical: Record<string, unknown>[] = [];\n for (const src of fm.sources) {\n const result = ProvenanceSourceSchema.safeParse(src);\n if (result.success) canonical.push(canonicalProvenanceSource(result.data));\n }\n if (canonical.length > 0) {\n lines.push(`sources: ${JSON.stringify(canonical)}`);\n hasValidSources = true;\n }\n }\n // A verified/unverified tag requires surviving evidence; without it the\n // tag is meaningless downstream (faithfulness/TrustScore cannot distinguish\n // it from a grounded fact). Downgrade to \"none\" regardless of WHY no source\n // survived (absent / empty / all-invalid) — single invariant, all cases.\n const tag =\n (fm.provenance === \"verified\" || fm.provenance === \"unverified\") && !hasValidSources\n ? \"none\"\n : fm.provenance;\n if (tag) {\n lines.push(`provenance: ${tag}`);\n }\n}\n\n/**\n * Parse the coarse `provenance` strength tag (issue #1575). Returns\n * `undefined` for missing/blank/unknown values so a corrupt or hand-edited\n * field fails safely to the legacy-equivalent `\"none\"` semantics on read\n * (rule 34 spirit — drop corrupt rather than poison).\n */\nexport function parseProvenanceTag(\n raw: string | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed === \"verified\" || trimmed === \"unverified\" || trimmed === \"none\") {\n return trimmed;\n }\n return undefined;\n}\n\n/**\n * Enforce the verified-requires-evidence invariant on the READ path.\n * `parseProvenanceTag` and `parseProvenanceSources` are independent (they\n * parse separate frontmatter lines), so a hand-edited or imported memory may\n * carry `provenance: verified` with no surviving `sources` — a corrupt line,\n * an empty array, or all-invalid entries. Downgrade such a tag to `none` so\n * the in-memory object never exposes an ungrounded \"verified\" fact\n * (review round 5, cursor thread KQN — read-path parity with the write-path\n * downgrade in `serializeProvenanceFields`). `none`/`undefined` tags pass\n * through unchanged.\n */\nexport function reconcileProvenanceRead(\n tag: \"verified\" | \"unverified\" | \"none\" | undefined,\n sources: ProvenanceSource[] | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if ((tag === \"verified\" || tag === \"unverified\") && (!sources || sources.length === 0)) {\n return \"none\";\n }\n return tag;\n}\n\n/**\n * Strict ISO-8601 timestamp check (review round 6, codex thread OXPAp).\n * `Date.parse` accepts non-ISO strings (bare years like `\"123\"`) and\n * silently normalizes calendar overflow (`2026-02-30` -> March 2, hour 25\n * -> next day), so malformed provenance survives as if valid. Require the\n * full `YYYY-MM-DDTHH:MM:SS[.fff](Z|±HH:MM)` shape and reject overflow via a\n * `Date.UTC` round-trip component check — the offset does not affect whether\n * a wall-clock field overflows, so this is correct for any timezone suffix.\n */\nfunction isStrictIsoTimestamp(s: string): boolean {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/.exec(s);\n if (!m) return false;\n const y = Number(m[1]), mo = Number(m[2]), da = Number(m[3]);\n const h = Number(m[4]), mi = Number(m[5]), se = Number(m[6]);\n // Date.UTC normalizes overflow (Feb 30 -> Mar 2); a component round-trip\n // catches what Date.parse silently accepts.\n const d = new Date(Date.UTC(y, mo - 1, da, h, mi, se));\n // The component round-trip validates wall-clock overflow (Feb 30 -> Mar 2);\n // Date.parse additionally rejects impossible timezone offsets such as\n // +99:99, which the regex accepts but the runtime treats as NaN\n // (codex thread OXQ0e).\n return (\n !Number.isNaN(Date.parse(s)) &&\n d.getUTCFullYear() === y &&\n d.getUTCMonth() === mo - 1 &&\n d.getUTCDate() === da &&\n d.getUTCHours() === h &&\n d.getUTCMinutes() === mi &&\n d.getUTCSeconds() === se\n );\n}\n\n/**\n * Coerce a turn timestamp to a strict ISO-8601 string (cursor thread Ocveu —\n * \"Non-ISO turn timestamps drop sources\"). The write-path `ProvenanceSourceSchema`\n * rejects anything `isStrictIsoTimestamp` fails, so a turn whose `timestamp`\n * parses via `new Date(...)` but isn't already strict ISO (e.g.\n * `\"2026/01/15 12:00:00\"` or `\"Jan 15 2026\"`) would be silently dropped at\n * serialization — clearing the whole `sources` array and downgrading the tag\n * to `\"none\"`. Normalizing at extraction time preserves the source.\n *\n * Returns the strict ISO string when the timestamp already passes or\n * `Date.parse` can round-trip it; `undefined` for empty / unparseable input\n * so callers can decide whether to skip the source or fall back.\n */\nfunction toStrictIsoTimestamp(ts: string | undefined | null): string | undefined {\n if (typeof ts !== \"string\" || ts.length === 0) return undefined;\n // Trim surrounding whitespace: Date.parse accepts leading/trailing\n // whitespace, so an imported \" 2026-05\" would otherwise bypass the\n // year-led partial-date guard below and fabricate May 1\n // (chatgpt-codex-connector review on #1714, issue #1657).\n const value = ts.trim();\n if (value.length === 0) return undefined;\n if (isStrictIsoTimestamp(value)) return value;\n const parsed = Date.parse(value);\n if (Number.isNaN(parsed)) return undefined;\n // Year-led date guard (issues #1657 + #1723). `parseYearLedDatePrefix`\n // recognizes a COMPLETE, calendar-valid year-led prefix — numeric OR textual\n // month — with real date-separator RUNS of non-alphanumeric, non-colon chars\n // (so complete-but-unusual-punctuation dates like \"2026 May, 10\",\n // \"2026, May 10\", \"2026 Jan. 15\" are accepted), the day-not-the-hour\n // invariant via the `(?![\\d:])` lookahead, and a calendar-valid day. This\n // single rule subsumes the prior partial-numeric (\"2026-05\"), textual-partial\n // (\"2026-Jan\"), and calendar-overflow (\"2026-02-30\", \"2026-Feb-30\") checks\n // and extends them to textual months — Date.parse silently fills missing\n // components and rolls overflow, fabricating observedAt, so a non-complete\n // or invalid year-led prefix is rejected. Non-year-led shapes\n // (\"Jan 15 2026\", \"02/30/2026\") fall through to Date.parse / the mdy check.\n const yearLedPrefix = parseYearLedDatePrefix(value);\n // Agreement check (issue #1723 codex r2/r3/r4): exotic punctuation can make\n // the prefix structure disagree with Date.parse's own interpretation — e.g.\n // \"2026 (May) 10\" parses as a valid May 10 PREFIX but Date.parse reads\n // October 1; \"2026 May (10)\" agrees on month but Date.parse reads day 1 — so\n // the round-trip would persist a fabricated observedAt while the guard\n // \"validated\" a different date. Require Date.parse's interpretation to match\n // the prefix's FULL (y, m, d) tuple. Offset-bearing inputs compare in the\n // source's explicit offset frame (the prefix date is offset-local, so a UTC\n // instant legitimately falls on an adjacent UTC/local day); offset-free\n // inputs compare both the local and UTC frames (Date.parse uses UTC for ISO\n // date-only strings and local for non-ISO — a single frame would wrongly\n // reject legitimate month/day-boundary dates that shift across timezones).\n const yearLedAgrees = yearLedAgreesWithDateParse(parsed, value, yearLedPrefix);\n const validYearLed = yearLedPrefix !== null && yearLedAgrees;\n // Reject bare-year / numeric-only strings that Date.parse accepts (e.g.\n // \"123\") — isStrictIsoTimestamp already rejected them, and the round-trip\n // below would otherwise resurrect them. Require at least one date/time\n // separator so a plain number never round-trips into a fake epoch — UNLESS\n // the value is itself a complete, calendar-valid year-led date that\n // Date.parse agrees with (issue #1723: a date-only punctuated textual form\n // like \"2026 May, 10\" has no -/:/T yet parses to the correct calendar day).\n if (!/[-:T]/.test(value) && !validYearLed) {\n return undefined;\n }\n // Reject year-led timestamps that are not a COMPLETE, calendar-valid date\n // Date.parse agrees with (covers partial, overflow, AND the punctuation\n // misinterpretation above).\n if (/^\\d{4}\\D/.test(value) && !validYearLed) {\n return undefined;\n }\n // Also validate M/D/Y or D/M/Y formats (provider/import common shapes:\n // 02/30/2026, 30-02-2026, etc.). Date.parse silently shifts overflow in\n // these too. Try both month-first and day-first interpretations; if\n // neither is a valid calendar date, reject (chatgpt-codex-connector thread\n // dH47 — non-YMD overflow timestamps).\n const mdy = /^(\\d{1,2})\\D(\\d{1,2})\\D(\\d{4})/.exec(value);\n if (mdy) {\n const a = Number(mdy[1]), b = Number(mdy[2]), yr = Number(mdy[3]);\n if (!isValidCalendarDate(yr, a, b) && !isValidCalendarDate(yr, b, a)) {\n return undefined;\n }\n }\n const iso = new Date(parsed).toISOString();\n return isStrictIsoTimestamp(iso) ? iso : undefined;\n}\n\n/**\n * Validate numeric calendar components without Date overflow (review thread\n * dANc). Date.UTC silently normalizes Feb 30 -> Mar 2; a component round-trip\n * catches what Date.parse accepts. Mirrors the same technique isStrictIsoTimestamp\n * uses for full ISO strings, applied here to the non-ISO normalization path.\n */\nfunction isValidCalendarDate(y: number, mo: number, da: number): boolean {\n if (mo < 1 || mo > 12 || da < 1) return false;\n const leap = (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)) ? 29 : 28;\n const daysInMonth = [31, leap, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n return da <= daysInMonth[mo - 1]!;\n}\n\n/**\n * Recognized month-name → number map for year-led textual-month dates\n * (issue #1657). Both the 3-letter abbreviations and the full names that\n * Date.parse accepts for year-first shapes (e.g. \"2026-Jan-15\",\n * \"2026-January-15\", \"2026 February 2\").\n */\nconst MONTH_NAMES: Record<string, number> = {\n jan: 1, january: 1,\n feb: 2, february: 2,\n mar: 3, march: 3,\n apr: 4, april: 4,\n may: 5,\n jun: 6, june: 6,\n jul: 7, july: 7,\n aug: 8, august: 8,\n sep: 9, september: 9,\n sept: 9,\n oct: 10, october: 10,\n nov: 11, november: 11,\n dec: 12, december: 12,\n};\n\n/**\n * Parse a complete, calendar-valid year-led date prefix from `s`\n * (issue #1657, relaxed in #1723): `YYYY <sep> (MM | MonthName) <sep> DD`\n * where each separator is a RUN of non-alphanumeric, non-colon chars (so a\n * complete date with any punctuation — \"2026 May, 10\", \"2026, May 10\",\n * \"2026 Jan. 15\" — is accepted; the run excludes letters so a textual month\n * is never consumed by the separator, and excludes the colon so the date/time\n * boundary stays clean) and the day is a true calendar day — not the hour\n * before a `:` time separator (the `(?![\\d:])` lookahead rejects\n * \"2026-05 10:00\" capturing \"10\" as the day). Returns the numeric components\n * for a complete, valid date; `null` for an incomplete (\"2026-05\",\n * \"2026-Jan\"), overflowed (\"2026-02-30\", \"2026-Feb-30\"), or non-year-led\n * prefix. Centralizes the fabricated-date rejection for the\n * toStrictIsoTimestamp normalization path across numeric and textual months\n * in one rule.\n */\nfunction parseYearLedDatePrefix(s: string): { y: number; mo: number; da: number } | null {\n const m = /^(\\d{4})[^0-9A-Za-z]+(\\d{1,2}|[A-Za-z]{3,})[^0-9A-Za-z:]+(\\d{1,2})(?![\\d:])/.exec(s);\n if (!m) return null;\n const y = Number(m[1]);\n const da = Number(m[3]);\n const mo = /^\\d+$/.test(m[2]) ? Number(m[2]) : (MONTH_NAMES[m[2]!.toLowerCase()] ?? -1);\n if (!isValidCalendarDate(y, mo, da)) return null;\n return { y, mo, da };\n}\n\n/**\n * Extract an explicit timezone offset (ms east of UTC) from a non-strict-ISO\n * timestamp, or `null` if none is present (issue #1723 codex r4). Recognizes\n * the offset forms `Date.parse` accepts for non-ISO year-led dates: a trailing\n * `±HH:MM` / `±HHMM`, and a `GMT`/`UTC` token optionally followed by\n * `±HHMM` (bare `GMT`/`UTC` ⇒ offset 0). Strict ISO `Z` forms are handled\n * by `isStrictIsoTimestamp` before this point, so they never reach here.\n */\n/**\n * Named timezone abbreviations Node's `Date.parse` accepts for non-ISO dates\n * (issue #1723 codex r8). V8 recognizes a fixed set of US zones; each maps to a\n * fixed offset (the abbreviation itself encodes standard vs daylight, so PST is\n * always -8 and PDT always -7 regardless of the calendar date). Other named\n * zones (CET/CEST/JST/…) return NaN from `Date.parse` and never reach here.\n */\nconst NAMED_ZONE_OFFSETS_MS: Record<string, number> = {\n PST: -8 * 60, PDT: -7 * 60,\n MST: -7 * 60, MDT: -6 * 60,\n CST: -6 * 60, CDT: -5 * 60,\n EST: -5 * 60, EDT: -4 * 60,\n};\n\nfunction explicitOffsetMs(value: string): number | null {\n // Recognize the offset forms Date.parse accepts for non-ISO year-led dates,\n // returning ms east of UTC or null. Tries each shape in order; a stray run\n // of date digits can never pose as an offset because (a) trailing numeric\n // offsets must be preceded by whitespace or glued to a \":SS\" seconds field\n // (lookbehind), and (b) every candidate is range-validated (|hh| ≤ 14,\n // mm < 59).\n // Named US zone abbreviation (PST/EST/EDT/…), trailing token (codex r8).\n const nz = /\\b([A-Z]{3,4})\\s*$/.exec(value);\n if (nz && NAMED_ZONE_OFFSETS_MS[nz[1]] !== undefined) {\n return NAMED_ZONE_OFFSETS_MS[nz[1]] * 60_000;\n }\n const tryOff = (sign: string | undefined, hh: number, mm: number): number | null => {\n if (hh > 14 || mm > 59) return null;\n return (sign === \"-\" ? -1 : 1) * (hh * 60 + mm) * 60_000;\n };\n let m: RegExpExecArray | null;\n // GMT/UTC token: colon form, then ±HHMM (4-digit, before bare hour so\n // \"GMT+0530\" is not read as \"+05\"), then bare hour, then the bare token\n // itself (offset 0).\n if (\n (m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{1,2}):(\\d{2})/i.exec(value)) ||\n (m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{2})(\\d{2})/i.exec(value))\n ) {\n const r = tryOff(m[1], Number(m[2]), Number(m[3]));\n if (r !== null) return r;\n }\n if ((m = /\\b(?:GMT|UTC)\\b\\s*([+-])?(\\d{1,2})\\b/i.exec(value))) {\n const r = tryOff(m[1], Number(m[2]), 0);\n if (r !== null) return r;\n }\n if (/\\b(?:GMT|UTC)\\b/i.test(value)) return 0;\n // Trailing numeric offset: whitespace-delimited OR glued to a \":SS\"\n // seconds field (lookbehind), so date digits (\"-0510\" in \"2026-0510\") are\n // never misread as an offset. Colon form, then ±HHMM, then bare hour — each\n // for BOTH the whitespace and glued contexts.\n if (\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{1,2}):(\\d{2})\\s*$/.exec(value)) ||\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{2})(\\d{2})\\s*$/.exec(value)) ||\n (m = /(?:\\s|(?<=:\\d{2}))([+-])(\\d{1,2})\\s*$/.exec(value))\n ) {\n const r = tryOff(m[1], Number(m[2]), m[3] ? Number(m[3]) : 0);\n if (r !== null) return r;\n }\n return null;\n}\n\n/**\n * Verify `Date.parse`'s interpretation of `value` agrees with the year-led\n * `prefix` components `(y, m, d)` (issue #1723 codex r2/r3/r4). Returns\n * `false` when `prefix` is `null` (no complete year-led date). Otherwise\n * compares the FULL tuple so punctuation that shifts the month OR the day is\n * caught. Offset-bearing inputs are compared in the source's explicit offset\n * frame (the prefix date is offset-local, so the same instant legitimately\n * lands on an adjacent UTC/local day); offset-free inputs accept either the\n * local or UTC frame, because `Date.parse` uses UTC for ISO date-only strings\n * and local for non-ISO forms.\n */\nfunction yearLedAgreesWithDateParse(\n parsed: number,\n value: string,\n prefix: { y: number; mo: number; da: number } | null,\n): boolean {\n if (prefix === null) return false;\n const off = explicitOffsetMs(value);\n if (off !== null) {\n const shifted = new Date(parsed + off);\n return (\n shifted.getUTCFullYear() === prefix.y &&\n shifted.getUTCMonth() + 1 === prefix.mo &&\n shifted.getUTCDate() === prefix.da\n );\n }\n const pd = new Date(parsed);\n const localMatch =\n pd.getFullYear() === prefix.y &&\n pd.getMonth() + 1 === prefix.mo &&\n pd.getDate() === prefix.da;\n const utcMatch =\n pd.getUTCFullYear() === prefix.y &&\n pd.getUTCMonth() + 1 === prefix.mo &&\n pd.getUTCDate() === prefix.da;\n return localMatch || utcMatch;\n}\n\n/**\n * Zod schema for a single `ProvenanceSource` entry (issue #1575). Parsed\n * JSON from frontmatter is external data, so each entry is validated here\n * rather than trusted via a cast (rule: no inline-cast-access on parsed\n * blobs). `safeParse` lets us drop corrupt entries individually instead of\n * failing the whole field.\n */\nconst ProvenanceSourceSchema = z\n .object({\n sessionKey: z.string().min(1),\n observedAt: z\n .string()\n .min(1)\n .refine(isStrictIsoTimestamp, \"must be a valid ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SS[Z|±HH:MM], no calendar overflow)\"),\n quote: z.string().min(1),\n turnId: z.string().min(1).optional(),\n charStart: z.number().finite().nonnegative().int().optional(),\n charEnd: z.number().finite().nonnegative().int().optional(),\n })\n .refine(\n (src) => src.charStart === undefined || src.charEnd === undefined || src.charEnd >= src.charStart,\n { message: \"charEnd must be >= charStart (half-open interval, rule 35)\" },\n );\n\n/**\n * Parse the `sources` array (issue #1575) from its single-line JSON form.\n * Mirrors `parseStructuredAttributes` (JSON.parse) but validates each entry\n * against `ProvenanceSourceSchema` and DROPS corrupt ones rather than\n * poisoning downstream readers — the same \"drop corrupt rather than poison\"\n * contract as `parseMemoryWorthCounterField`. If no entry survives, the\n * whole field is `undefined` (legacy-equivalent).\n */\nexport function parseProvenanceSources(raw: string | undefined): ProvenanceSource[] | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n if (!Array.isArray(parsed)) return undefined;\n const sources: ProvenanceSource[] = [];\n for (const entry of parsed) {\n const result = ProvenanceSourceSchema.safeParse(entry);\n if (result.success) sources.push(result.data);\n }\n return sources.length > 0 ? sources : undefined;\n}\n\n/**\n * Parse the `provenance` config block (issue #1575 PR 1). Validates the\n * shape before applying defaults — a shorthand like `provenance: false` must\n * reject loudly rather than normalize to `{}` and silently enable the feature\n * (rule 51). Booleans coerce via `coerceBool` (rule 36); numeric cap clamps\n * at 1 (rule 28). `REMNIC_PROVENANCE_ENABLED` / `ENGRAM_PROVENANCE_ENABLED`\n * are honored only when the `enabled` key is omitted (explicit config wins).\n *\n * Schema-default note (review rounds 1–3, settled): `provenance.enabled` has\n * NO `\"default\"` in any plugin.json schema. OpenClaw's loader runs\n * `applyDefaults: true` before exposing `api.pluginConfig` (src/index.ts:1345,\n * PR #1593 round 8), so a schema default would be materialized into the\n * merged config and mask the `REMNIC_`/`ENGRAM_` env override. The code-level\n * default-on here (`return true` when `enabled` is omitted) supplies the\n * default-on behavior without that materialization. This matches the\n * emitLegacyTools/namespaceCatalogEnabled precedent, which omits the env\n * override only after a raw-vs-effective split — overkill for a single\n * boolean, so this field uses the simpler omit-the-default approach.\n */\nexport function parseProvenanceConfig(raw: unknown): ProvenanceConfig {\n if (\n raw !== undefined &&\n (raw === null || typeof raw !== \"object\" || Array.isArray(raw))\n ) {\n throw new Error(\n `provenance must be an object (got ${JSON.stringify(raw)}). Use provenance: { enabled: false } to opt out; omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n const rawProvenance =\n raw && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n return {\n enabled: (() => {\n if (rawProvenance.enabled === undefined) {\n const envEnabled =\n readEnvVar(\"REMNIC_PROVENANCE_ENABLED\") ?? readEnvVar(\"ENGRAM_PROVENANCE_ENABLED\");\n if (envEnabled !== undefined) {\n const coerced = coerceBool(envEnabled);\n if (coerced === undefined) {\n throw new Error(\n `REMNIC_PROVENANCE_ENABLED must be a boolean-like value (true/false/1/0/yes/no/on/off); got ${JSON.stringify(envEnabled)}`,\n );\n }\n return coerced;\n }\n return true;\n }\n const coerced = coerceBool(rawProvenance.enabled);\n if (coerced === undefined) {\n throw new Error(\n `provenance.enabled must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.enabled)}). Omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n return coerced;\n })(),\n maxQuoteChars: (() => {\n if (rawProvenance.maxQuoteChars === undefined) return 300;\n const rawCap = coerceNumber(rawProvenance.maxQuoteChars);\n // Reject present-but-invalid rather than silently widening the cap\n // (AGENTS.md input-validation rule — a typo should not persist more\n // text than the operator configured).\n if (rawCap === undefined || !Number.isFinite(rawCap) || rawCap < 1 || !Number.isInteger(rawCap)) {\n throw new Error(\n `provenance.maxQuoteChars must be a positive integer >= 1 (got ${JSON.stringify(rawProvenance.maxQuoteChars)}).`,\n );\n }\n return rawCap;\n })(),\n requireSpans: (() => {\n if (rawProvenance.requireSpans === undefined) return false;\n const coerced = coerceBool(rawProvenance.requireSpans);\n if (coerced === undefined) {\n throw new Error(\n `provenance.requireSpans must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.requireSpans)}).`,\n );\n }\n return coerced;\n })(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1575 PR 2 — extraction-side post-parse validator.\n// ---------------------------------------------------------------------------\n//\n// This runs once at write time (inside `ExtractionEngine.extract`, after the\n// LLM output is parsed and sanitized, before the result is returned for\n// persistence). Its job: locate each fact's LLM-provided `quote` in the\n// buffered turn texts and build a `ProvenanceSource[]` with verified\n// offsets. Never throws, never drops a fact (rule 34 spirit — an\n// unverifiable span is a tagged state, not a silent failure).\n//\n// Reuses `collapseWhitespace` from `whitespace.ts` for normalization so there\n// is exactly one normalizer in the codebase (issue #1575 pitfall: \"do not\n// write a second normalizer\").\n// ---------------------------------------------------------------------------\n\n/**\n * A turn in the buffered conversation, reduced to the fields the provenance\n * validator needs. `ExtractionEngine.extract` maps its `BufferTurn[]` to this\n * shape so the validator is pure and testable without the full BufferTurn type.\n */\nexport interface ProvenanceTurnInput {\n content: string;\n sessionKey?: string;\n logicalSessionKey?: string;\n timestamp: string;\n turnId?: string;\n}\n\n/**\n * Result of building provenance for a single extracted fact.\n */\nexport interface ProvenanceBuildResult {\n /** Verified sources (one per matching turn). Absent when no quote survives. */\n sources?: ProvenanceSource[];\n /** Coarse strength tag persisted to frontmatter. */\n provenance: \"verified\" | \"unverified\" | \"none\";\n /**\n * Transient signal (never persisted): `true` when `config.requireSpans`\n * is enabled and the LLM-provided quote could not be located in ANY source\n * turn (the strict case `ProvenanceConfig.requireSpans` documents: \"facts\n * whose quote cannot be located are routed to `pending_review`\"). The\n * extraction consumer carries this onto the in-memory `ExtractedFact` so\n * the persist path can route the fact to the review queue. A quote that\n * WAS located but whose source was dropped (e.g. un-coercible timestamp)\n * does NOT set this flag — the span was found, so requireSpans is\n * satisfied (chatgpt-codex-connector thread 4xB).\n */\n requireSpansPending?: boolean;\n}\n\n/**\n * Cap a quote string at `maxChars`, truncating at the last word boundary that\n * fits and appending an ellipsis marker. Quotes at or under the cap pass\n * through unchanged. Operates on Unicode code points (not UTF-16 units) so\n * emoji and astral-plane characters are not split mid-glyph.\n */\nfunction capQuote(quote: string, maxChars: number): string {\n const glyphs = Array.from(quote);\n if (glyphs.length <= maxChars) return quote;\n // Walk backward from the cap to find a word boundary (space). If the entire\n // span is one long token (no spaces), cut at the cap — a hard cut is better\n // than no quote at all.\n let cut = maxChars;\n while (cut > 0 && !/\\s/.test(glyphs[cut - 1]!)) cut--;\n if (cut === 0) cut = maxChars; // no word boundary found\n return glyphs.slice(0, cut).join(\"\").trimEnd() + \"\\u2026\";\n}\n\n/**\n * Casefold a string for normalized matching. Uses `toLowerCase` (not\n * `toLocaleLowerCase`) for determinism across runtime locales — the existing\n * `normalizeFactKey` helper in extraction.ts follows the same convention.\n */\nfunction casefold(s: string): string {\n return s.toLowerCase();\n}\n\n/**\n * Locate `quote` within `text`, returning whether a match was found and, when\n * recoverable, the half-open `[start, end)` offsets. Tries exact substring\n * first, then whitespace/case-normalized match.\n *\n * For the normalized path, the mapping from the normalized string back to\n * original offsets is recovered by walking both strings in lockstep: we know\n * `collapseWhitespace(text)` is a subsequence of `text` with whitespace runs\n * collapsed, so we can track the original offset as we scan.\n *\n * Returns `{ matched: false }` when neither match succeeds. Returns\n * `{ matched: true, offsets }` when offsets are recoverable. Returns\n * `{ matched: true }` (offsets omitted) when the normalized substring was\n * found but original offsets could not be recovered — callers still treat\n * this as a verified match and record a source without offsets (cursor\n * thread Ocver — \"Normalized match drops verified provenance\").\n */\nexport type LocateQuoteResult =\n | { matched: false }\n | { matched: true; offsets?: { charStart: number; charEnd: number } };\n\nexport function locateQuoteOffsets(quote: string, text: string): LocateQuoteResult {\n // 1. Exact substring match (handles unicode, curly quotes, emoji verbatim).\n const exactIdx = text.indexOf(quote);\n if (exactIdx >= 0) {\n return { matched: true, offsets: { charStart: exactIdx, charEnd: exactIdx + quote.length } };\n }\n\n // 2. Whitespace/case-normalized match. Collapse runs of whitespace and\n // casefold both sides, then find the normalized quote in the normalized\n // text. Recover original offsets by scanning forward from the normalized\n // match start through the original text, accumulating non-whitespace\n // glyphs until we've consumed the normalized quote length.\n const normQuote = collapseWhitespace(casefold(quote));\n if (normQuote.length === 0) return { matched: false };\n const normText = collapseWhitespace(casefold(text));\n const normIdx = normText.indexOf(normQuote);\n if (normIdx < 0) return { matched: false };\n\n // Recover original offsets: walk the original text, skipping leading\n // whitespace to align with the collapsed form, then track how many\n // normalized chars we've consumed.\n const normQuoteLen = normQuote.length;\n let origIdx = 0;\n // Skip leading whitespace in original text (collapseWhitespace trims both ends).\n while (origIdx < text.length && /\\s/.test(text[origIdx]!)) origIdx++;\n // Walk through original text, counting normalized chars consumed.\n // Each non-whitespace char in original = 1 normalized char.\n // Whitespace runs in original = 1 space in normalized (but only between non-ws).\n let normPos = 0;\n let origStart = -1;\n let origEnd = -1;\n let prevWasWs = true; // suppress the leading space in collapsed form\n while (origIdx < text.length && origEnd < 0) {\n const ch = text[origIdx]!;\n if (/\\s/.test(ch)) {\n if (!prevWasWs) {\n // This whitespace run collapses to a single space in normalized text.\n if (normPos === normIdx) origStart = origIdx; // normalized space aligns\n normPos++;\n prevWasWs = true;\n }\n origIdx++;\n continue;\n }\n // Non-whitespace char.\n if (normPos === normIdx && origStart < 0) {\n origStart = origIdx;\n }\n normPos++;\n prevWasWs = false;\n origIdx++;\n if (normPos >= normIdx + normQuoteLen) {\n origEnd = origIdx;\n }\n }\n if (origStart >= 0 && origEnd >= 0) {\n return { matched: true, offsets: { charStart: origStart, charEnd: origEnd } };\n }\n // Normalized substring was found, but original offsets are not recoverable\n // (edge case in the normalized mapping). The match still counts as verified\n // — record a source without offsets rather than dropping the turn entirely\n // (cursor thread Ocver). charStart/charEnd are best-effort debugging aids,\n // not a precondition for a verified source.\n return { matched: true };\n}\n\n/**\n * Build provenance sources for a single extracted fact by locating its\n * LLM-provided `quote` in the buffered turns (issue #1575 PR 2).\n *\n * Matching strategy (per issue design):\n * 1. Exact substring match in a turn → `provenance: \"verified\"` with\n * `charStart`/`charEnd` offsets.\n * 2. Whitespace/case-normalized match → `\"verified\"`, offsets recovered\n * when possible, else omitted.\n * 3. No match in any turn → `\"unverified\"` — the quote survives as a\n * source (the LLM vouched for it) but without located offsets.\n * 4. No quote provided by the LLM → `\"none\"` — no evidence to record.\n *\n * A quote appearing in multiple turns produces multiple sources (one per\n * matching turn) so a repeated utterance backs the fact from each occurrence.\n *\n * The quote is capped at `config.maxQuoteChars` (truncated at a word boundary\n * with an ellipsis marker) BEFORE storage. Locating uses the original\n * (untruncated) quote for maximum match fidelity; the capped excerpt is what\n * gets persisted.\n *\n * When `config.enabled === false`, returns `{ provenance: \"none\" }`\n * immediately — byte-identical to pre-feature extraction (rule 39).\n *\n * Never throws. An unexpected error degrades to `{ provenance: \"none\" }` so\n * extraction never crashes on a provenance hiccup (rule 13/18).\n */\n/**\n * Strip a leading extraction-prompt role label from a quote (cursor thread Oc3Z2\n * — \"Quote prompt mismatches validator\"). The extraction prompt renders each\n * buffered turn as `[role] content` (or `[context role] content`), so a\n * faithful LLM may include that prefix in its verbatim quote. buildFactProvenance\n * searches the raw `turn.content` (no prefix), so a quote carrying the label\n * would never match. Stripping the leading label lets the actual utterance\n * verify against the turn text. Only a SINGLE leading label is stripped — a\n * multi-turn quote (with embedded labels) is left untouched and will simply\n * fail to match a single turn, as before.\n *\n * The regex is constrained to the labels the prompt actually emits — `user`,\n * `assistant`, optionally prefixed with `context ` (extraction.ts renders\n * `[user]`, `[assistant]`, `[context user]`, `[context assistant]`). A\n * real utterance that happens to start with other bracketed text (e.g.\n * `[do not] deploy before approval`, `[P1] fix the cache`) is preserved\n * verbatim so the quote can match the turn and the persisted span retains its\n * original meaning (chatgpt-codex-connector thread 4xA — limiting role-prefix\n * stripping to actual prompt labels).\n */\nexport function stripLeadingRolePrefix(quote: string): string {\n return quote.replace(/^\\s*\\[(?:context\\s+)?(?:user|assistant)\\]\\s+/i, \"\");\n}\n\nexport function buildFactProvenance(\n factQuote: string | null | undefined,\n turns: ReadonlyArray<ProvenanceTurnInput>,\n config: ProvenanceConfig,\n): ProvenanceBuildResult {\n if (!config.enabled) return { provenance: \"none\" };\n const rawQuote = typeof factQuote === \"string\" ? factQuote.trim() : \"\";\n // requireSpans (chatgpt-codex-connector thread dEsu + cursor thread dGKJ):\n // every early exit that drops a fact's span (no quote, empty after label\n // strip, or unsafe quote) must flag requireSpansPending when the operator\n // opted into requireSpans, so the persist path routes the fact to\n // pending_review instead of active. Only the disabled-feature exit (above)\n // and the unexpected-error catch (below) omit the flag — those are\n // policy-neutral degradations, not a missing-span decision.\n const noneResult = (): ProvenanceBuildResult =>\n config.requireSpans === true\n ? { provenance: \"none\", requireSpansPending: true }\n : { provenance: \"none\" };\n if (rawQuote.length === 0) return noneResult();\n // Strip a leading prompt role label so a faithful quote verifies against\n // the raw turn content (cursor thread Oc3Z2). Prefer the RAW quote when it\n // matches at least one turn — an utterance that literally begins with\n // [user]/[assistant] must verify as-is, not be truncated to the post-label\n // text (cursor/codex thread dEsw). The strip handles the common case where\n // the LLM includes the prompt label; this preserves the rare case where the\n // utterance itself starts with that text.\n const strippedQuote = stripLeadingRolePrefix(rawQuote);\n const useRawQuote =\n rawQuote === strippedQuote ||\n turns.some(\n (t) =>\n typeof t?.content === \"string\" &&\n t.content.length > 0 &&\n locateQuoteOffsets(rawQuote, t.content).matched,\n );\n const quote = useRawQuote ? rawQuote : strippedQuote;\n if (quote.length === 0) return noneResult();\n // Sanitize the quote before persisting it as a provenance span\n // (chatgpt-codex-connector thread dANZ): the fact body is sanitized via\n // sanitizeMemoryContent, but sources[].quote was persisted verbatim,\n // reintroducing unsafe memory text (e.g. \"ignore previous instructions\")\n // through the provenance field exposed to memory_get/x-ray/faithfulness.\n // An unsafe quote cannot serve as evidence — drop the source entirely\n // (consistent with how the body is sanitized: unsafe text is redacted,\n // and a redacted quote is useless as a verbatim span).\n if (!isSafeMemoryContent(quote)) return noneResult();\n\n try {\n // Search every turn for the quote. Collect verified sources.\n const sources: ProvenanceSource[] = [];\n // Track the first turn where the quote was located even when its\n // timestamp can't be coerced (cursor thread 4Pj — \"Bad timestamp drops\n // matched source\"). Without this, a located-but-unverifiable quote falls\n // through to the unverified branch and is attributed to the *last* turn's\n // session, mislabeling the source's origin session. Also drives the\n // requireSpans signal: only a quote that was NOT located in any turn\n // qualifies for pending_review routing under requireSpans (thread 4xB).\n let locatedTurn: ProvenanceTurnInput | undefined;\n for (const turn of turns) {\n if (!turn || typeof turn.content !== \"string\" || turn.content.length === 0) continue;\n const located = locateQuoteOffsets(quote, turn.content);\n // cursor thread Ocver: a normalized match counts as verified even when\n // original offsets are unrecoverable — record the source without\n // charStart/charEnd instead of skipping the turn.\n if (!located.matched) continue;\n if (!locatedTurn) locatedTurn = turn;\n // cursor thread Ocveu: normalize the turn timestamp to strict ISO so\n // the write-path ProvenanceSourceSchema keeps the source. Skip the turn\n // when the timestamp can't be coerced — pushing it would guarantee a\n // serialization drop (and the tag downgrade) for this source.\n const observedAt = toStrictIsoTimestamp(turn.timestamp);\n if (!observedAt) continue;\n sources.push({\n sessionKey: turn.sessionKey ?? turn.logicalSessionKey ?? \"unknown\",\n ...(turn.turnId ? { turnId: turn.turnId } : {}),\n observedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n ...(located.offsets\n ? { charStart: located.offsets.charStart, charEnd: located.offsets.charEnd }\n : {}),\n });\n }\n\n if (sources.length > 0) {\n return { sources, provenance: \"verified\" };\n }\n\n // Quote provided but could not be turned into a verified source.\n // Determine the session to attribute: prefer the turn where the quote was\n // LOCATED even if its timestamp couldn't be coerced (cursor thread 4Pj — a\n // located quote must not be tied to the *last* turn's session). If the\n // quote was not located in any turn at all, fall back to the last turn's\n // session (the documented unverified behavior: the LLM vouched for the\n // excerpt but we couldn't pin it to a character offset). Normalize the\n // timestamp (thread Ocveu); fall back to epoch when no turn supplies a\n // coercible timestamp so the unverified source still survives the\n // write-path schema.\n const fallbackTurn = locatedTurn ?? turns[turns.length - 1];\n const fallbackSessionKey = fallbackTurn\n ? (fallbackTurn.sessionKey ?? fallbackTurn.logicalSessionKey ?? \"unknown\")\n : \"unknown\";\n const fallbackObservedAt = fallbackTurn\n ? (toStrictIsoTimestamp(fallbackTurn.timestamp) ?? new Date(0).toISOString())\n : new Date(0).toISOString();\n // requireSpans signal (chatgpt-codex-connector thread 4xB): when an\n // operator opts into provenance.requireSpans, a fact whose quote could\n // not be located in ANY turn (locatedTurn undefined) is flagged so the\n // persist path routes it to pending_review instead of active. A quote\n // that WAS located (locatedTurn set) satisfies requireSpans even when\n // its source was dropped for an un-coercible timestamp — the span was\n // found, so the fact has the grounding requireSpans demands.\n const requireSpansPending =\n config.requireSpans === true && locatedTurn === undefined;\n return {\n sources: [\n {\n sessionKey: fallbackSessionKey,\n observedAt: fallbackObservedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n },\n ],\n provenance: \"unverified\",\n ...(requireSpansPending ? { requireSpansPending: true } : {}),\n };\n } catch {\n // Never crash extraction on a provenance error (rule 13/18).\n return { provenance: \"none\" };\n }\n}\n"],"mappings":"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","names":["coerced"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SupportPassportCreateGrantRequestSchema,
3
3
  defineOperation
4
- } from "./chunk-HXDXR2FH.js";
4
+ } from "./chunk-AS4YKT75.js";
5
5
  import {
6
6
  SupportPassportCardCategorySchema,
7
7
  SupportPassportMemoryIdSchema,
@@ -220,4 +220,4 @@ var supportPassportGrantAskOperation = defineOperation({
220
220
  result: await ctx.service.supportPassportAskGrant(grantId, secret, question, ctx.abortSignal)
221
221
  })
222
222
  });
223
- //# sourceMappingURL=chunk-FMEP2RB5.js.map
223
+ //# sourceMappingURL=chunk-SJ7ZNUXB.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  resolveDefaultNamespaceRoot,
3
3
  resolveNamespaceStorageRoot
4
- } from "./chunk-J5OXWG66.js";
4
+ } from "./chunk-XZUHXKM3.js";
5
5
  import {
6
6
  MutationSerializer,
7
7
  withHeldFileLock
@@ -1473,4 +1473,4 @@ export {
1473
1473
  isNamespaceKind,
1474
1474
  NamespaceCatalog
1475
1475
  };
1476
- //# sourceMappingURL=chunk-EEHQY7ER.js.map
1476
+ //# sourceMappingURL=chunk-SK6HEQTW.js.map
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VEWZZM3H.js";
8
8
  import {
9
9
  buildFactProvenance
10
- } from "./chunk-RW4QNB4J.js";
10
+ } from "./chunk-S45INSV3.js";
11
11
 
12
12
  // src/extraction-source-grounding.ts
13
13
  function applyExtractionSourceGrounding2(result, sourceText, assertionSourceText = sourceText, roleAssertionSources, messageTimestamp, options) {
@@ -70,4 +70,4 @@ export {
70
70
  attachExtractionProvenance,
71
71
  filterExtractionResultBySource2 as filterExtractionResultBySource
72
72
  };
73
- //# sourceMappingURL=chunk-PGAB2V53.js.map
73
+ //# sourceMappingURL=chunk-SMBWBEE7.js.map
@@ -3,7 +3,7 @@ import {
3
3
  readNamespaceMaintenanceLastRanStatuses,
4
4
  readNamespaceMaintenanceStatuses,
5
5
  runNamespaceMaintenancePlan
6
- } from "./chunk-WOBMSMUK.js";
6
+ } from "./chunk-WYU66IVZ.js";
7
7
  import {
8
8
  analyzeSessionIntegrity,
9
9
  applySessionRepair,
@@ -14,7 +14,7 @@ import {
14
14
  } from "./chunk-CMTINOFS.js";
15
15
  import {
16
16
  summarizeCorpusAndReplica
17
- } from "./chunk-S3SND7SO.js";
17
+ } from "./chunk-LIDJWAQV.js";
18
18
  import {
19
19
  readJudgeVerdictStats
20
20
  } from "./chunk-OZKOPQCM.js";
@@ -38,23 +38,23 @@ import {
38
38
  } from "./chunk-YBPYIAA5.js";
39
39
  import {
40
40
  parseConfig
41
- } from "./chunk-W6YDAIVB.js";
41
+ } from "./chunk-OSTXIFZW.js";
42
42
  import {
43
43
  readAggregateExtractionWatermark
44
- } from "./chunk-DDBLZJRV.js";
44
+ } from "./chunk-QB4VXCFN.js";
45
45
  import {
46
46
  listMemoryGovernanceRuns,
47
47
  readMemoryGovernanceRunArtifact
48
- } from "./chunk-S7KDHFF2.js";
48
+ } from "./chunk-YXWMZAAN.js";
49
49
  import {
50
50
  listNamespaces
51
- } from "./chunk-47C6DF43.js";
51
+ } from "./chunk-OS4JVTHR.js";
52
52
  import {
53
53
  analyzeGraphHealth
54
54
  } from "./chunk-5FAGCOZD.js";
55
55
  import {
56
56
  StorageManager
57
- } from "./chunk-QUFJ3NJE.js";
57
+ } from "./chunk-MNG536IS.js";
58
58
  import {
59
59
  lintWorkspaceFiles
60
60
  } from "./chunk-DM2T26WE.js";
@@ -1683,4 +1683,4 @@ export {
1683
1683
  runBenchmarkRecall,
1684
1684
  runOperatorRepair
1685
1685
  };
1686
- //# sourceMappingURL=chunk-62NYUFNH.js.map
1686
+ //# sourceMappingURL=chunk-SS33RR4F.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-B7RWBZXW.js";
4
4
  import {
5
5
  StorageManager
6
- } from "./chunk-QUFJ3NJE.js";
6
+ } from "./chunk-MNG536IS.js";
7
7
  import {
8
8
  getCachedEpisodeMap,
9
9
  setCachedEpisodeMap
@@ -109,4 +109,4 @@ export {
109
109
  compareVerifiedEpisodeResults,
110
110
  searchVerifiedEpisodes
111
111
  };
112
- //# sourceMappingURL=chunk-4ECS33D4.js.map
112
+ //# sourceMappingURL=chunk-T3T5HTZZ.js.map
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  resolveEnsureCollectionArgs
3
3
  } from "./chunk-U2VDCFXW.js";
4
+ import {
5
+ EMPTY_QMD_STATUS,
6
+ embedQmdFiles,
7
+ fetchQmdStatus
8
+ } from "./chunk-IUM3VSHI.js";
4
9
  import {
5
10
  QMD_PROBE_RETRY_BACKOFF_MS,
6
11
  QMD_PROBE_TIMEOUT_MS,
@@ -13,11 +18,6 @@ import {
13
18
  qmdVersionToString,
14
19
  resolveQmdCapabilities
15
20
  } from "./chunk-ZLQ37WJX.js";
16
- import {
17
- EMPTY_QMD_STATUS,
18
- embedQmdFiles,
19
- fetchQmdStatus
20
- } from "./chunk-IUM3VSHI.js";
21
21
  import {
22
22
  clearQmdResultCaches,
23
23
  getCachedQmdSearch,
@@ -2253,4 +2253,4 @@ export {
2253
2253
  sharedDaemonSessionCountForTest,
2254
2254
  QmdClient
2255
2255
  };
2256
- //# sourceMappingURL=chunk-KG2OOAFF.js.map
2256
+ //# sourceMappingURL=chunk-TQSCR5DU.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SupportPassportError,
3
3
  getOperation
4
- } from "./chunk-HXDXR2FH.js";
4
+ } from "./chunk-AS4YKT75.js";
5
5
  import {
6
6
  abortError,
7
7
  isAbortError
@@ -499,4 +499,4 @@ export {
499
499
  buildSupportPassportPublicRequestHandler,
500
500
  composeSupportPassportExternalRequestHandlers
501
501
  };
502
- //# sourceMappingURL=chunk-OV7GJ4KW.js.map
502
+ //# sourceMappingURL=chunk-V2XNMIAM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RECALL_DISCLOSURE_LEVELS,
3
3
  isRecallDisclosure
4
- } from "./chunk-66YUOI53.js";
4
+ } from "./chunk-WQW6KPVC.js";
5
5
 
6
6
  // src/recall-navigate-disclosure.ts
7
7
  function disclosureRank(level) {
@@ -27,4 +27,4 @@ export {
27
27
  disclosureRank,
28
28
  planDisclosureStep
29
29
  };
30
- //# sourceMappingURL=chunk-AS2NOPDM.js.map
30
+ //# sourceMappingURL=chunk-V7IONNTJ.js.map
@@ -2,7 +2,7 @@ import {
2
2
  WHO_KNOWS_DEFAULT_LIMIT,
3
3
  WHO_KNOWS_MAX_LIMIT,
4
4
  validateWhoKnowsInput
5
- } from "./chunk-FSH6KRKO.js";
5
+ } from "./chunk-PPJLJD2D.js";
6
6
 
7
7
  // src/who-knows-cli.ts
8
8
  function extractWhoKnowsRawArgs(rest) {
@@ -74,4 +74,4 @@ export {
74
74
  renderWhoKnowsText,
75
75
  renderWhoKnows
76
76
  };
77
- //# sourceMappingURL=chunk-HYWPUYLO.js.map
77
+ //# sourceMappingURL=chunk-VV5GCNKG.js.map
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-PIWFNPQT.js";
8
8
  import {
9
9
  StorageManager
10
- } from "./chunk-QUFJ3NJE.js";
10
+ } from "./chunk-MNG536IS.js";
11
11
  import {
12
12
  projectProceduresToSkills
13
13
  } from "./chunk-236PXYG7.js";
@@ -134,4 +134,4 @@ export {
134
134
  runCodexMaterialize,
135
135
  runPostConsolidationMaterialize
136
136
  };
137
- //# sourceMappingURL=chunk-Y3XHIWVP.js.map
137
+ //# sourceMappingURL=chunk-WL3PGPGU.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-7RGX7BMF.js";
4
4
  import {
5
5
  deepRecallRequestSchema
6
- } from "./chunk-AYP2FZFA.js";
6
+ } from "./chunk-JUOJGZPV.js";
7
7
  import {
8
8
  EngramAccessInputError
9
9
  } from "./chunk-2Z24E5W2.js";
@@ -40,4 +40,4 @@ async function respondDeepRecall(req, res, respondJson, readJsonBody, service, s
40
40
  export {
41
41
  respondDeepRecall
42
42
  };
43
- //# sourceMappingURL=chunk-3UQIS2GC.js.map
43
+ //# sourceMappingURL=chunk-WNMVUWI7.js.map
@@ -19,4 +19,4 @@ export {
19
19
  confidenceTier,
20
20
  SPECULATIVE_TTL_DAYS
21
21
  };
22
- //# sourceMappingURL=chunk-66YUOI53.js.map
22
+ //# sourceMappingURL=chunk-WQW6KPVC.js.map