@remnic/core 10.0.14 → 10.0.16

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 (366) hide show
  1. package/dist/abstraction-nodes.js +3 -2
  2. package/dist/access-admin-ops-surface.js +34 -32
  3. package/dist/access-authorization-probe.js +35 -33
  4. package/dist/access-boundary.js +34 -32
  5. package/dist/access-cli.js +90 -88
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +34 -32
  8. package/dist/access-http-briefing.js +35 -33
  9. package/dist/access-http-query.js +36 -34
  10. package/dist/access-http.js +53 -51
  11. package/dist/access-identity-continuity-surface.js +34 -32
  12. package/dist/access-lcm-surface.js +34 -32
  13. package/dist/access-mcp.js +45 -43
  14. package/dist/access-namespace-preflight.js +34 -32
  15. package/dist/access-observe-write-category.js +4 -3
  16. package/dist/access-observe-write-surface.js +34 -32
  17. package/dist/access-offline-manifest.js +3 -2
  18. package/dist/access-operations-batch.js +37 -35
  19. package/dist/access-operations.js +44 -42
  20. package/dist/access-recall-concurrency.js +34 -32
  21. package/dist/access-recall-response.js +34 -32
  22. package/dist/access-recall-surface.js +34 -32
  23. package/dist/access-schema.js +3 -2
  24. package/dist/access-service.js +34 -32
  25. package/dist/access-surface-catalog.js +36 -34
  26. package/dist/access-surface-catalog.js.map +1 -1
  27. package/dist/active-recall.js +14 -12
  28. package/dist/active-recall.js.map +1 -1
  29. package/dist/briefing.js +10 -8
  30. package/dist/buffer.js +2 -2
  31. package/dist/calibration.d.ts.map +1 -1
  32. package/dist/calibration.js +6 -3
  33. package/dist/calibration.js.map +1 -1
  34. package/dist/causal-behavior.js +4 -3
  35. package/dist/causal-behavior.js.map +1 -1
  36. package/dist/causal-chain.js +4 -3
  37. package/dist/causal-consolidation.js +14 -12
  38. package/dist/causal-consolidation.js.map +1 -1
  39. package/dist/causal-retrieval.js +4 -3
  40. package/dist/causal-retrieval.js.map +1 -1
  41. package/dist/causal-trajectory.js +3 -2
  42. package/dist/{chunk-63B2IZYE.js → chunk-23ZQPP2G.js} +6 -1
  43. package/dist/chunk-23ZQPP2G.js.map +1 -0
  44. package/dist/{chunk-YFMBHQRG.js → chunk-2PTH4PRL.js} +2 -2
  45. package/dist/{chunk-TITG2NBS.js → chunk-2PYHLSXK.js} +2 -2
  46. package/dist/{chunk-B6O2YLUR.js → chunk-2Z5DIDQD.js} +6 -1
  47. package/dist/chunk-2Z5DIDQD.js.map +1 -0
  48. package/dist/{chunk-KH2LKDX7.js → chunk-33G67YZF.js} +2 -2
  49. package/dist/{chunk-SWIKABKI.js → chunk-3Q3XD3NO.js} +2 -2
  50. package/dist/{chunk-4KY5GSMB.js → chunk-4PDTVVSW.js} +2 -2
  51. package/dist/{chunk-HM7RHKS4.js → chunk-4RNBLLZR.js} +2 -2
  52. package/dist/{chunk-PAZ35RJ4.js → chunk-4SCZZ3U7.js} +240 -55
  53. package/dist/chunk-4SCZZ3U7.js.map +1 -0
  54. package/dist/{chunk-Y4NKEXQT.js → chunk-5ATSXCAG.js} +3 -3
  55. package/dist/{chunk-JDZFIR2S.js → chunk-5KGYTX5O.js} +2 -2
  56. package/dist/{chunk-J7JWFZHI.js → chunk-5MLGY6IY.js} +39 -39
  57. package/dist/{chunk-NW2E4CI2.js → chunk-5SMR542T.js} +2 -2
  58. package/dist/{chunk-S7LWX2PQ.js → chunk-655FVTCD.js} +2 -2
  59. package/dist/{chunk-4OSG5Y6M.js → chunk-6YEPGWUP.js} +2 -2
  60. package/dist/{chunk-W47S5TIH.js → chunk-7VQ6AI7H.js} +2 -2
  61. package/dist/{chunk-WLMZ2L5P.js → chunk-AZDTDWS2.js} +2 -2
  62. package/dist/{chunk-G7NYVKD6.js → chunk-B2NXOVZN.js} +2 -2
  63. package/dist/{chunk-N7KYEVLK.js → chunk-BK2BVLZW.js} +2 -2
  64. package/dist/{chunk-QJ622LTI.js → chunk-BQOGQULP.js} +30 -8
  65. package/dist/chunk-BQOGQULP.js.map +1 -0
  66. package/dist/{chunk-S2I7DL5Q.js → chunk-C2RW36YR.js} +2 -2
  67. package/dist/{chunk-P5NQN4JE.js → chunk-C6JMGAMO.js} +6 -2
  68. package/dist/chunk-C6JMGAMO.js.map +1 -0
  69. package/dist/{chunk-6LG7BWKW.js → chunk-CFZ4SXXH.js} +2 -2
  70. package/dist/{chunk-JQ7XU625.js → chunk-CQOIWJ5E.js} +6 -6
  71. package/dist/{chunk-FSIMZNBC.js → chunk-CXOYUANR.js} +2 -2
  72. package/dist/{chunk-BLVHDY2U.js → chunk-DOWACHYO.js} +5 -2
  73. package/dist/chunk-DOWACHYO.js.map +1 -0
  74. package/dist/{chunk-QOZJCSFO.js → chunk-DQR2LD3V.js} +2 -2
  75. package/dist/{chunk-MATDGSJ7.js → chunk-DUPHIZBY.js} +2 -2
  76. package/dist/{chunk-EBA5DA77.js → chunk-EOU6V4IN.js} +2 -2
  77. package/dist/{chunk-5WCJLQTM.js → chunk-F2772ATT.js} +2 -2
  78. package/dist/{chunk-46EST7JO.js → chunk-F2XNSIPP.js} +2 -2
  79. package/dist/{chunk-PZHQWFR3.js → chunk-G3UUAQOD.js} +2 -2
  80. package/dist/{chunk-YE36AJL7.js → chunk-GCVDFX66.js} +2 -2
  81. package/dist/{chunk-ZV65I6ME.js → chunk-GFWRXPF6.js} +29 -26
  82. package/dist/chunk-GFWRXPF6.js.map +1 -0
  83. package/dist/{chunk-T2YX56QI.js → chunk-GQJKVQJR.js} +2 -2
  84. package/dist/{chunk-I7S55EOB.js → chunk-GUY6L6LL.js} +2 -2
  85. package/dist/{chunk-L5DAHSEZ.js → chunk-GWVNGDJW.js} +2 -2
  86. package/dist/{chunk-N45NEBOD.js → chunk-GZXXPCWJ.js} +3 -3
  87. package/dist/{chunk-EPJDSQHF.js → chunk-H5IUN433.js} +2 -2
  88. package/dist/{chunk-EUAIYYYR.js → chunk-H6FREOYX.js} +7 -7
  89. package/dist/{chunk-Q4E24IRR.js → chunk-HDN7IVIO.js} +14 -11
  90. package/dist/chunk-HDN7IVIO.js.map +1 -0
  91. package/dist/{chunk-T3FMRLWQ.js → chunk-HFSPY26A.js} +2 -2
  92. package/dist/{chunk-GH7AOMJD.js → chunk-IJMDBYCO.js} +2 -2
  93. package/dist/{chunk-4IHBN6WU.js → chunk-JKMIGCK6.js} +2 -2
  94. package/dist/{chunk-ROBHL3GL.js → chunk-JWX2Q6HS.js} +2 -2
  95. package/dist/{chunk-DPR2WTN3.js → chunk-JZTEOBOR.js} +16 -16
  96. package/dist/{chunk-4O6WRBGE.js → chunk-KJXP6RUG.js} +5 -1
  97. package/dist/chunk-KJXP6RUG.js.map +1 -0
  98. package/dist/{chunk-JIHMUOOS.js → chunk-KLJVTZ3R.js} +3 -3
  99. package/dist/{chunk-FP4JYE66.js → chunk-KW4AJ7T3.js} +2 -2
  100. package/dist/{chunk-NIRQGDYC.js → chunk-L5VCT7LU.js} +5 -1
  101. package/dist/chunk-L5VCT7LU.js.map +1 -0
  102. package/dist/{chunk-QODKDRSP.js → chunk-L6AZOEXV.js} +4 -4
  103. package/dist/{chunk-CLWBFR7Q.js → chunk-LO76EF3Y.js} +1 -1
  104. package/dist/chunk-LO76EF3Y.js.map +1 -0
  105. package/dist/{chunk-LS2FHUML.js → chunk-LQ5SS4OP.js} +3 -3
  106. package/dist/{chunk-SIIRTDJ4.js → chunk-LWFWZTWX.js} +2 -2
  107. package/dist/{chunk-BMWN7B3Y.js → chunk-LXATZLSC.js} +3 -3
  108. package/dist/{chunk-O4SGYW7H.js → chunk-MAVYEZKM.js} +2 -2
  109. package/dist/{chunk-B665A6BO.js → chunk-MJIOGYKX.js} +2 -2
  110. package/dist/{chunk-RTKV5XHL.js → chunk-MO6N4QYY.js} +2 -2
  111. package/dist/{chunk-N4C7QKOG.js → chunk-MWWAQXZ6.js} +2 -2
  112. package/dist/{chunk-LCKVXX5F.js → chunk-MXV2MY6C.js} +2 -2
  113. package/dist/{chunk-FFOVKNQV.js → chunk-NAGMQXWV.js} +2 -2
  114. package/dist/{chunk-ZJVMUBYF.js → chunk-NHUX6GXA.js} +6 -6
  115. package/dist/{chunk-CB6ANA2I.js → chunk-NZCKG25S.js} +2 -2
  116. package/dist/{chunk-UMTCNEVQ.js → chunk-O2YPCFJT.js} +11 -11
  117. package/dist/{chunk-WSGPFRBH.js → chunk-OMERTLB7.js} +3 -3
  118. package/dist/{chunk-QX6JN3O4.js → chunk-PEF5LHQU.js} +6 -6
  119. package/dist/{chunk-JYZSRNR7.js → chunk-PIITB2YZ.js} +2 -2
  120. package/dist/chunk-PPUNC7UX.js +73 -0
  121. package/dist/chunk-PPUNC7UX.js.map +1 -0
  122. package/dist/{chunk-EQWEIIKQ.js → chunk-PQM3B54K.js} +6 -1
  123. package/dist/chunk-PQM3B54K.js.map +1 -0
  124. package/dist/{chunk-UQDG3YLN.js → chunk-PRU7OF3Y.js} +3 -3
  125. package/dist/{chunk-EU534WGS.js → chunk-PVHQSLNN.js} +7 -7
  126. package/dist/{chunk-TZNDKTX5.js → chunk-Q7DWEXG2.js} +37 -33
  127. package/dist/chunk-Q7DWEXG2.js.map +1 -0
  128. package/dist/{chunk-5VC7J76U.js → chunk-QPH52AZB.js} +2 -2
  129. package/dist/{chunk-XISROV24.js → chunk-QSEUUQUF.js} +2 -2
  130. package/dist/chunk-QUIRIG2P.js +31 -0
  131. package/dist/chunk-QUIRIG2P.js.map +1 -0
  132. package/dist/{chunk-FUFAXVBA.js → chunk-RI2KCQHP.js} +6 -2
  133. package/dist/chunk-RI2KCQHP.js.map +1 -0
  134. package/dist/{chunk-O2IPAX26.js → chunk-RTTPBBAN.js} +4 -4
  135. package/dist/{chunk-W5MCEVUS.js → chunk-RYYVMZBR.js} +2 -2
  136. package/dist/{chunk-ZSH2NRDV.js → chunk-TN4EA4X4.js} +2 -2
  137. package/dist/{chunk-SS3IS2JR.js → chunk-UGYUBENJ.js} +2 -2
  138. package/dist/{chunk-GELS5Z5Z.js → chunk-V3TXIXXY.js} +3 -3
  139. package/dist/{chunk-WVADKFWV.js → chunk-VNZMZPAC.js} +2 -2
  140. package/dist/{chunk-JPTPS4OB.js → chunk-VPXIKBPX.js} +2 -2
  141. package/dist/{chunk-OP6N4H6A.js → chunk-W3FVQBGF.js} +2 -2
  142. package/dist/{chunk-W3K3HITK.js → chunk-WLT6LTQP.js} +2 -2
  143. package/dist/{chunk-GMXWTJNU.js → chunk-XKRXN63M.js} +2 -2
  144. package/dist/{chunk-KR6ZKECI.js → chunk-XYGQSDTH.js} +3 -3
  145. package/dist/{chunk-22W2XWGH.js → chunk-YBO2WGCV.js} +2 -2
  146. package/dist/{chunk-P7TOIOT7.js → chunk-YR4COAEC.js} +2 -2
  147. package/dist/{chunk-6WHJ2EAB.js → chunk-Z64TS5V4.js} +2 -2
  148. package/dist/{chunk-NHLLKEKK.js → chunk-ZAJLJ35W.js} +2 -2
  149. package/dist/cli.d.ts.map +1 -1
  150. package/dist/cli.js +73 -71
  151. package/dist/coding/export-okf-codegraph.js +9 -7
  152. package/dist/coding/export-okf-codegraph.js.map +1 -1
  153. package/dist/coding/pre-action-gate.js +4 -3
  154. package/dist/commitment-ledger.js +3 -2
  155. package/dist/compounding/engine.js +11 -9
  156. package/dist/config.js +14 -12
  157. package/dist/connectors/codex-materialize-runner.js +10 -8
  158. package/dist/connectors/index.js +10 -8
  159. package/dist/consolidation-provenance-check.d.ts.map +1 -1
  160. package/dist/consolidation-provenance-check.js +2 -1
  161. package/dist/contradiction/index.js +3 -2
  162. package/dist/convergence-refresh.js +12 -10
  163. package/dist/corpus-watermark.js +12 -10
  164. package/dist/correction/correction-planner.d.ts.map +1 -1
  165. package/dist/cue-anchors.js +4 -3
  166. package/dist/deep-recall-http-glue.js +4 -3
  167. package/dist/{dreams-ledger-567VKVOU.js → dreams-ledger-UCHHRQ4R.js} +3 -2
  168. package/dist/entity-retrieval.js +10 -8
  169. package/dist/eval-trajectory-store.js +4 -3
  170. package/dist/eval-trajectory.js +4 -3
  171. package/dist/evals.js +3 -2
  172. package/dist/external-wiki-access.js +35 -33
  173. package/dist/extraction.js +14 -12
  174. package/dist/{first-start-migration-6A5BJPDW.js → first-start-migration-QVD22WWS.js} +6 -5
  175. package/dist/{first-start-migration-6A5BJPDW.js.map → first-start-migration-QVD22WWS.js.map} +1 -1
  176. package/dist/harmonic-construction.js +5 -4
  177. package/dist/harmonic-retrieval.js +14 -12
  178. package/dist/harmonic-source-lookup.js +3 -2
  179. package/dist/index.js +90 -88
  180. package/dist/json-store.d.ts.map +1 -1
  181. package/dist/json-store.js +2 -1
  182. package/dist/location/index.js +12 -10
  183. package/dist/maintenance/dreams-ledger.d.ts.map +1 -1
  184. package/dist/maintenance/memory-governance.d.ts.map +1 -1
  185. package/dist/maintenance/memory-governance.js +10 -8
  186. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -8
  187. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -10
  188. package/dist/maintenance/rebuild-memory-projection.js +11 -9
  189. package/dist/memory-browse-operations.js +35 -33
  190. package/dist/namespaces/migrate.js +11 -9
  191. package/dist/namespaces/search.js +7 -6
  192. package/dist/namespaces/storage.d.ts.map +1 -1
  193. package/dist/namespaces/storage.js +10 -8
  194. package/dist/objective-state-writers.js +4 -3
  195. package/dist/objective-state.js +3 -2
  196. package/dist/offline-sync-snapshot-stream.js +3 -2
  197. package/dist/offline-sync.d.ts.map +1 -1
  198. package/dist/offline-sync.js +2 -1
  199. package/dist/operator-doctor-corpus.js +13 -11
  200. package/dist/operator-toolkit.js +25 -23
  201. package/dist/orchestration/extraction-run.d.ts +6 -20
  202. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  203. package/dist/orchestration/maintenance.js +20 -18
  204. package/dist/orchestrator.js +90 -88
  205. package/dist/profiling.d.ts.map +1 -1
  206. package/dist/profiling.js +2 -1
  207. package/dist/recall-navigation-operations.js +35 -33
  208. package/dist/recall-navigation.js +34 -32
  209. package/dist/{resolution-7AY36LGJ.js → resolution-ZU5BIAJA.js} +4 -3
  210. package/dist/resume-bundles.js +19 -17
  211. package/dist/retrieval-agents.d.ts +1 -1
  212. package/dist/retrieval-agents.d.ts.map +1 -1
  213. package/dist/retrieval-agents.js +7 -1
  214. package/dist/search/document-scanner.d.ts.map +1 -1
  215. package/dist/search/document-scanner.js +2 -1
  216. package/dist/search/factory.js +6 -5
  217. package/dist/search/index.js +6 -5
  218. package/dist/search/lancedb-backend.js +3 -2
  219. package/dist/search/meilisearch-backend.js +3 -2
  220. package/dist/search/orama-backend.js +3 -2
  221. package/dist/semantic-consolidation.js +11 -9
  222. package/dist/semantic-rule-promotion.js +10 -8
  223. package/dist/semantic-rule-verifier.js +10 -8
  224. package/dist/source-agent-qualifier.js +12 -10
  225. package/dist/standup/build.js +10 -8
  226. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  227. package/dist/storage/entity-store.d.ts +32 -1
  228. package/dist/storage/entity-store.d.ts.map +1 -1
  229. package/dist/storage/memory-read-store.d.ts.map +1 -1
  230. package/dist/storage/supersession-audit.d.ts.map +1 -1
  231. package/dist/storage.d.ts.map +1 -1
  232. package/dist/storage.js +9 -7
  233. package/dist/support-passport/index.js +37 -35
  234. package/dist/{tier-stats-RQM76XYY.js → tier-stats-C3IAMS4X.js} +6 -5
  235. package/dist/{tier-stats-RQM76XYY.js.map → tier-stats-C3IAMS4X.js.map} +1 -1
  236. package/dist/transcript.d.ts.map +1 -1
  237. package/dist/transcript.js +2 -1
  238. package/dist/transfer/export-okf.js +10 -8
  239. package/dist/trust-zones.js +3 -2
  240. package/dist/utility-learner.js +4 -3
  241. package/dist/utility-runtime.js +5 -4
  242. package/dist/utility-telemetry.js +3 -2
  243. package/dist/utils/macos-replication-junk.d.ts +32 -0
  244. package/dist/utils/macos-replication-junk.d.ts.map +1 -0
  245. package/dist/utils/malformed-entity-record.d.ts +24 -0
  246. package/dist/utils/malformed-entity-record.d.ts.map +1 -0
  247. package/dist/verified-recall.js +10 -8
  248. package/dist/who-knows-cli.js +11 -9
  249. package/dist/who-knows.js +10 -8
  250. package/dist/work-product-ledger.js +3 -2
  251. package/package.json +2 -2
  252. package/src/calibration.ts +3 -2
  253. package/src/cli.ts +2 -2
  254. package/src/consolidation-provenance-check.ts +2 -0
  255. package/src/correction/correction-planner.ts +3 -2
  256. package/src/json-store.ts +3 -0
  257. package/src/macos-replication-junk-scanners.test.ts +275 -0
  258. package/src/maintenance/dreams-ledger.ts +2 -0
  259. package/src/maintenance/memory-governance.ts +2 -0
  260. package/src/namespaces/storage.ts +2 -0
  261. package/src/offline-sync-macos-junk.test.ts +169 -0
  262. package/src/offline-sync.ts +7 -0
  263. package/src/orchestration/extraction-run.ts +6 -20
  264. package/src/profiling.ts +2 -1
  265. package/src/retrieval-agents.ts +29 -12
  266. package/src/search/document-scanner.ts +4 -0
  267. package/src/storage/entity-canonical-id-migration.ts +56 -13
  268. package/src/storage/entity-store.ts +173 -15
  269. package/src/storage/memory-read-store.ts +30 -5
  270. package/src/storage/supersession-audit.ts +2 -0
  271. package/src/storage.ts +3 -2
  272. package/src/transcript.ts +11 -10
  273. package/src/utils/macos-replication-junk.test.ts +46 -0
  274. package/src/utils/macos-replication-junk.ts +65 -0
  275. package/src/utils/malformed-entity-record.ts +101 -0
  276. package/dist/chunk-4O6WRBGE.js.map +0 -1
  277. package/dist/chunk-63B2IZYE.js.map +0 -1
  278. package/dist/chunk-B6O2YLUR.js.map +0 -1
  279. package/dist/chunk-BLVHDY2U.js.map +0 -1
  280. package/dist/chunk-CLWBFR7Q.js.map +0 -1
  281. package/dist/chunk-EQWEIIKQ.js.map +0 -1
  282. package/dist/chunk-FUFAXVBA.js.map +0 -1
  283. package/dist/chunk-NIRQGDYC.js.map +0 -1
  284. package/dist/chunk-P5NQN4JE.js.map +0 -1
  285. package/dist/chunk-PAZ35RJ4.js.map +0 -1
  286. package/dist/chunk-Q4E24IRR.js.map +0 -1
  287. package/dist/chunk-QJ622LTI.js.map +0 -1
  288. package/dist/chunk-TZNDKTX5.js.map +0 -1
  289. package/dist/chunk-ZV65I6ME.js.map +0 -1
  290. /package/dist/{chunk-YFMBHQRG.js.map → chunk-2PTH4PRL.js.map} +0 -0
  291. /package/dist/{chunk-TITG2NBS.js.map → chunk-2PYHLSXK.js.map} +0 -0
  292. /package/dist/{chunk-KH2LKDX7.js.map → chunk-33G67YZF.js.map} +0 -0
  293. /package/dist/{chunk-SWIKABKI.js.map → chunk-3Q3XD3NO.js.map} +0 -0
  294. /package/dist/{chunk-4KY5GSMB.js.map → chunk-4PDTVVSW.js.map} +0 -0
  295. /package/dist/{chunk-HM7RHKS4.js.map → chunk-4RNBLLZR.js.map} +0 -0
  296. /package/dist/{chunk-Y4NKEXQT.js.map → chunk-5ATSXCAG.js.map} +0 -0
  297. /package/dist/{chunk-JDZFIR2S.js.map → chunk-5KGYTX5O.js.map} +0 -0
  298. /package/dist/{chunk-J7JWFZHI.js.map → chunk-5MLGY6IY.js.map} +0 -0
  299. /package/dist/{chunk-NW2E4CI2.js.map → chunk-5SMR542T.js.map} +0 -0
  300. /package/dist/{chunk-S7LWX2PQ.js.map → chunk-655FVTCD.js.map} +0 -0
  301. /package/dist/{chunk-4OSG5Y6M.js.map → chunk-6YEPGWUP.js.map} +0 -0
  302. /package/dist/{chunk-W47S5TIH.js.map → chunk-7VQ6AI7H.js.map} +0 -0
  303. /package/dist/{chunk-WLMZ2L5P.js.map → chunk-AZDTDWS2.js.map} +0 -0
  304. /package/dist/{chunk-G7NYVKD6.js.map → chunk-B2NXOVZN.js.map} +0 -0
  305. /package/dist/{chunk-N7KYEVLK.js.map → chunk-BK2BVLZW.js.map} +0 -0
  306. /package/dist/{chunk-S2I7DL5Q.js.map → chunk-C2RW36YR.js.map} +0 -0
  307. /package/dist/{chunk-6LG7BWKW.js.map → chunk-CFZ4SXXH.js.map} +0 -0
  308. /package/dist/{chunk-JQ7XU625.js.map → chunk-CQOIWJ5E.js.map} +0 -0
  309. /package/dist/{chunk-FSIMZNBC.js.map → chunk-CXOYUANR.js.map} +0 -0
  310. /package/dist/{chunk-QOZJCSFO.js.map → chunk-DQR2LD3V.js.map} +0 -0
  311. /package/dist/{chunk-MATDGSJ7.js.map → chunk-DUPHIZBY.js.map} +0 -0
  312. /package/dist/{chunk-EBA5DA77.js.map → chunk-EOU6V4IN.js.map} +0 -0
  313. /package/dist/{chunk-5WCJLQTM.js.map → chunk-F2772ATT.js.map} +0 -0
  314. /package/dist/{chunk-46EST7JO.js.map → chunk-F2XNSIPP.js.map} +0 -0
  315. /package/dist/{chunk-PZHQWFR3.js.map → chunk-G3UUAQOD.js.map} +0 -0
  316. /package/dist/{chunk-YE36AJL7.js.map → chunk-GCVDFX66.js.map} +0 -0
  317. /package/dist/{chunk-T2YX56QI.js.map → chunk-GQJKVQJR.js.map} +0 -0
  318. /package/dist/{chunk-I7S55EOB.js.map → chunk-GUY6L6LL.js.map} +0 -0
  319. /package/dist/{chunk-L5DAHSEZ.js.map → chunk-GWVNGDJW.js.map} +0 -0
  320. /package/dist/{chunk-N45NEBOD.js.map → chunk-GZXXPCWJ.js.map} +0 -0
  321. /package/dist/{chunk-EPJDSQHF.js.map → chunk-H5IUN433.js.map} +0 -0
  322. /package/dist/{chunk-EUAIYYYR.js.map → chunk-H6FREOYX.js.map} +0 -0
  323. /package/dist/{chunk-T3FMRLWQ.js.map → chunk-HFSPY26A.js.map} +0 -0
  324. /package/dist/{chunk-GH7AOMJD.js.map → chunk-IJMDBYCO.js.map} +0 -0
  325. /package/dist/{chunk-4IHBN6WU.js.map → chunk-JKMIGCK6.js.map} +0 -0
  326. /package/dist/{chunk-ROBHL3GL.js.map → chunk-JWX2Q6HS.js.map} +0 -0
  327. /package/dist/{chunk-DPR2WTN3.js.map → chunk-JZTEOBOR.js.map} +0 -0
  328. /package/dist/{chunk-JIHMUOOS.js.map → chunk-KLJVTZ3R.js.map} +0 -0
  329. /package/dist/{chunk-FP4JYE66.js.map → chunk-KW4AJ7T3.js.map} +0 -0
  330. /package/dist/{chunk-QODKDRSP.js.map → chunk-L6AZOEXV.js.map} +0 -0
  331. /package/dist/{chunk-LS2FHUML.js.map → chunk-LQ5SS4OP.js.map} +0 -0
  332. /package/dist/{chunk-SIIRTDJ4.js.map → chunk-LWFWZTWX.js.map} +0 -0
  333. /package/dist/{chunk-BMWN7B3Y.js.map → chunk-LXATZLSC.js.map} +0 -0
  334. /package/dist/{chunk-O4SGYW7H.js.map → chunk-MAVYEZKM.js.map} +0 -0
  335. /package/dist/{chunk-B665A6BO.js.map → chunk-MJIOGYKX.js.map} +0 -0
  336. /package/dist/{chunk-RTKV5XHL.js.map → chunk-MO6N4QYY.js.map} +0 -0
  337. /package/dist/{chunk-N4C7QKOG.js.map → chunk-MWWAQXZ6.js.map} +0 -0
  338. /package/dist/{chunk-LCKVXX5F.js.map → chunk-MXV2MY6C.js.map} +0 -0
  339. /package/dist/{chunk-FFOVKNQV.js.map → chunk-NAGMQXWV.js.map} +0 -0
  340. /package/dist/{chunk-ZJVMUBYF.js.map → chunk-NHUX6GXA.js.map} +0 -0
  341. /package/dist/{chunk-CB6ANA2I.js.map → chunk-NZCKG25S.js.map} +0 -0
  342. /package/dist/{chunk-UMTCNEVQ.js.map → chunk-O2YPCFJT.js.map} +0 -0
  343. /package/dist/{chunk-WSGPFRBH.js.map → chunk-OMERTLB7.js.map} +0 -0
  344. /package/dist/{chunk-QX6JN3O4.js.map → chunk-PEF5LHQU.js.map} +0 -0
  345. /package/dist/{chunk-JYZSRNR7.js.map → chunk-PIITB2YZ.js.map} +0 -0
  346. /package/dist/{chunk-UQDG3YLN.js.map → chunk-PRU7OF3Y.js.map} +0 -0
  347. /package/dist/{chunk-EU534WGS.js.map → chunk-PVHQSLNN.js.map} +0 -0
  348. /package/dist/{chunk-5VC7J76U.js.map → chunk-QPH52AZB.js.map} +0 -0
  349. /package/dist/{chunk-XISROV24.js.map → chunk-QSEUUQUF.js.map} +0 -0
  350. /package/dist/{chunk-O2IPAX26.js.map → chunk-RTTPBBAN.js.map} +0 -0
  351. /package/dist/{chunk-W5MCEVUS.js.map → chunk-RYYVMZBR.js.map} +0 -0
  352. /package/dist/{chunk-ZSH2NRDV.js.map → chunk-TN4EA4X4.js.map} +0 -0
  353. /package/dist/{chunk-SS3IS2JR.js.map → chunk-UGYUBENJ.js.map} +0 -0
  354. /package/dist/{chunk-GELS5Z5Z.js.map → chunk-V3TXIXXY.js.map} +0 -0
  355. /package/dist/{chunk-WVADKFWV.js.map → chunk-VNZMZPAC.js.map} +0 -0
  356. /package/dist/{chunk-JPTPS4OB.js.map → chunk-VPXIKBPX.js.map} +0 -0
  357. /package/dist/{chunk-OP6N4H6A.js.map → chunk-W3FVQBGF.js.map} +0 -0
  358. /package/dist/{chunk-W3K3HITK.js.map → chunk-WLT6LTQP.js.map} +0 -0
  359. /package/dist/{chunk-GMXWTJNU.js.map → chunk-XKRXN63M.js.map} +0 -0
  360. /package/dist/{chunk-KR6ZKECI.js.map → chunk-XYGQSDTH.js.map} +0 -0
  361. /package/dist/{chunk-22W2XWGH.js.map → chunk-YBO2WGCV.js.map} +0 -0
  362. /package/dist/{chunk-P7TOIOT7.js.map → chunk-YR4COAEC.js.map} +0 -0
  363. /package/dist/{chunk-6WHJ2EAB.js.map → chunk-Z64TS5V4.js.map} +0 -0
  364. /package/dist/{chunk-NHLLKEKK.js.map → chunk-ZAJLJ35W.js.map} +0 -0
  365. /package/dist/{dreams-ledger-567VKVOU.js.map → dreams-ledger-UCHHRQ4R.js.map} +0 -0
  366. /package/dist/{resolution-7AY36LGJ.js.map → resolution-ZU5BIAJA.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/orchestration/extraction-run-helpers.ts","../src/orchestration/extraction-run.ts","../src/orchestration/extraction-origin-context.ts"],"sourcesContent":["import type { BufferTurn, ExtractionFailureClass, ExtractionResult, MetaState } from \"../types.js\";\nimport type { StorageManager } from \"../index.js\";\nimport { log } from \"../logger.js\";\n\nexport function combineExtractionAbortSignals(\n primary: AbortSignal | undefined,\n deadlineController: AbortController | undefined,\n): AbortSignal | undefined {\n if (!deadlineController) return primary;\n return primary ? AbortSignal.any([primary, deadlineController.signal]) : deadlineController.signal;\n}\n\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function deriveTopicsFromExtraction(result: ExtractionResult): string[] {\n if (!isRecord(result)) return [];\n const topics = new Set<string>();\n const facts = Array.isArray(result.facts) ? result.facts : [];\n for (const rawFact of facts) {\n if (!isRecord(rawFact)) continue;\n const tags = Array.isArray(rawFact.tags) ? rawFact.tags : [];\n for (const tag of tags) {\n if (typeof tag === \"string\" && tag.length >= 2) topics.add(tag.toLowerCase());\n }\n if (typeof rawFact.entityRef === \"string\" && rawFact.entityRef.length >= 2) {\n topics.add(rawFact.entityRef.toLowerCase());\n }\n if (typeof rawFact.category === \"string\" && rawFact.category.length > 0) {\n topics.add(rawFact.category);\n }\n }\n const entities = Array.isArray(result.entities) ? result.entities : [];\n for (const rawEntity of entities) {\n if (!isRecord(rawEntity)) continue;\n if (typeof rawEntity.name === \"string\" && rawEntity.name.length >= 2) {\n topics.add(rawEntity.name.toLowerCase());\n }\n }\n return [...topics].slice(0, 16);\n}\n\n/** Bounded cap on persisted per-fingerprint retry-state entries (per namespace meta). */\nexport const EXTRACTION_RETRY_STATE_MAX_ENTRIES = 500;\n\nexport interface ExtractionRetryStateEntry {\n attempts: number;\n nextEligibleAtMs: number;\n firstFailedAtMs: number;\n lastFailureClass: ExtractionFailureClass;\n}\n\nexport interface ExtractionResilienceStatus {\n breaker: {\n state: \"closed\" | \"open\" | \"half_open\";\n openUntilMs: number;\n consecutiveFailures: number;\n lastReason: string;\n };\n backoffFingerprintCount: number;\n}\n\n/**\n * Pure backoff scheduler. Returns the absolute ms timestamp at which a\n * fingerprint that has failed `attempts` times becomes eligible again:\n * exponential via the configured schedule, clamped to `maxBackoffMs`, with\n * ±`jitterRatio` multiplicative jitter. `rng` is injectable for deterministic\n * tests. Never returns a timestamp before `now`.\n */\nexport function computeExtractionRetryNextEligibleMs(\n attempts: number,\n scheduleMs: readonly number[],\n maxBackoffMs: number,\n jitterRatio: number,\n now: number,\n rng: () => number = Math.random,\n): number {\n if (!Number.isFinite(attempts) || !Number.isInteger(attempts) || attempts < 1) {\n throw new RangeError(\"attempts must be a finite integer >= 1\");\n }\n if (!Number.isFinite(maxBackoffMs) || maxBackoffMs < 0) {\n throw new RangeError(\"maxBackoffMs must be a finite number >= 0\");\n }\n if (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) {\n throw new RangeError(\"jitterRatio must be a finite number in [0, 1]\");\n }\n const safeNow = Number.isFinite(now) ? now : 0;\n const cap = maxBackoffMs;\n if (!Array.isArray(scheduleMs) || scheduleMs.length === 0) {\n return safeNow + cap;\n }\n const idx = Math.min(attempts - 1, scheduleMs.length - 1);\n const step = scheduleMs[idx];\n const base = Math.min(typeof step === \"number\" && Number.isFinite(step) && step > 0 ? step : cap, cap);\n const randomValue = rng();\n if (!Number.isFinite(randomValue) || randomValue < 0 || randomValue > 1) {\n throw new RangeError(\"rng() must return a finite number in [0, 1]\");\n }\n const jitter = 1 + (randomValue * 2 - 1) * jitterRatio;\n return safeNow + Math.max(0, Math.round(base * jitter));\n}\n\n/**\n * Cap the persisted retry-state array to the newest `maxEntries` by\n * firstFailedAt. Guards invalid caps so they cannot turn `slice(-maxEntries)`\n * into an unbounded copy (AGENTS.md rule 17).\n */\nexport function capExtractionRetryStateEntries<T extends { firstFailedAt: string }>(\n entries: readonly T[],\n maxEntries: number,\n): T[] {\n if (!Number.isFinite(maxEntries) || !Number.isInteger(maxEntries) || maxEntries <= 0) return [];\n const sorted = [...entries].sort((a, b) => a.firstFailedAt.localeCompare(b.firstFailedAt));\n return sorted.slice(-maxEntries);\n}\n\n/**\n * Derive a single `sourceConnector` from a batch of turns.\n * Returns the connector ONLY when ALL turns carry the same connector.\n * Mixed tagged/untagged batches return undefined — extraction facts are\n * persisted at batch level, so attributing them to the single tagged\n * connector would falsely label facts from untagged turns.\n * Conflicting connectors (different values) also return undefined.\n */\nexport function deriveSourceConnector(turns: readonly BufferTurn[]): string | undefined {\n if (turns.length === 0) return undefined;\n const connectors = new Set<string>();\n let untaggedCount = 0;\n for (const turn of turns) {\n if (typeof turn.sourceConnector === \"string\" && turn.sourceConnector.length > 0) {\n connectors.add(turn.sourceConnector);\n } else {\n untaggedCount += 1;\n }\n }\n // All turns must agree on the same connector — no untagged turns mixed in.\n if (untaggedCount > 0) return undefined;\n return connectors.size === 1 ? [...connectors][0] : undefined;\n}\n\nexport async function runExtractionPostPersistBestEffort(\n runDeadlineAware: (\n operation: () => Promise<unknown>,\n phase: string,\n clearTimerOnError?: boolean,\n options?: { ignoreAbort?: boolean; ignoreDeadline?: boolean },\n ) => Promise<unknown>,\n stage: string,\n operation: () => Promise<unknown>,\n options?: { ignoreAbort?: boolean; ignoreDeadline?: boolean; propagateErrors?: boolean },\n): Promise<void> {\n try {\n await runDeadlineAware(operation, stage, false, {\n ignoreAbort: options?.ignoreAbort,\n ignoreDeadline: options?.ignoreDeadline,\n });\n } catch (error) {\n if (options?.propagateErrors) throw error;\n log.warn(`runExtraction: ${stage} failed after persistence (non-fatal)`, error);\n }\n}\n\n/**\n * Applies deferred-judge retention only to live, non-shadow extraction runs.\n * Replay/import paths must not synthesize live-buffer entries after persistence.\n */\nexport interface ExtractionDeferRetentionOptions {\n clearBufferAfterExtraction: boolean;\n extractionJudgeShadow: boolean;\n getDeferredCount: () => number;\n normalizedTurns: BufferTurn[];\n bufferKey: string;\n retainDeferredTurns: (bufferKey: string, turns: BufferTurn[], max: number) => Promise<void>;\n}\n\nexport async function runExtractionDeferRetention(\n runPostPersistBestEffort: (\n stage: string,\n operation: () => Promise<unknown>,\n options?: { ignoreAbort?: boolean },\n ) => Promise<void>,\n options: ExtractionDeferRetentionOptions,\n): Promise<void> {\n await runPostPersistBestEffort(\"during_defer_retention\", async () => {\n if (options.clearBufferAfterExtraction && !options.extractionJudgeShadow) {\n const deferredCount = options.getDeferredCount();\n if (deferredCount > 0 && options.normalizedTurns.length > 0) {\n await options.retainDeferredTurns(options.bufferKey, options.normalizedTurns, 10);\n } else {\n await options.retainDeferredTurns(options.bufferKey, [], 0);\n }\n }\n });\n}\n\nexport interface ExtractionPostPersistOptions {\n result: ExtractionResult;\n storage: StorageManager;\n meta?: MetaState;\n extractionFingerprint?: string;\n shouldPersistProcessedFingerprint: boolean;\n recordProcessedExtractionFingerprint: (\n storage: StorageManager,\n fingerprint: string,\n meta: MetaState,\n ) => Promise<void>;\n runDeadlineAware: (\n operation: () => Promise<unknown>,\n phase: string,\n clearTimerOnError?: boolean,\n options?: { ignoreAbort?: boolean; ignoreDeadline?: boolean },\n ) => Promise<unknown>;\n isDeadlineError: (error: unknown) => boolean;\n createDeadlineError: (stage: string) => Error;\n deadlineMs?: number;\n clearBufferAfterExtraction: boolean;\n runPostPersistBestEffort: (\n stage: string,\n operation: () => Promise<unknown>,\n options?: { ignoreAbort?: boolean; ignoreDeadline?: boolean; propagateErrors?: boolean },\n ) => Promise<void>;\n extractionJudgeShadow: boolean;\n getDeferredCount: () => number;\n normalizedTurns: BufferTurn[];\n bufferKey: string;\n retainDeferredTurns: (bufferKey: string, turns: BufferTurn[], max: number) => Promise<void>;\n clearBuffer: (options?: { ignoreAbort?: boolean }) => Promise<void>;\n failOnExtractionFailure: boolean;\n}\n\nexport interface ExtractionPostPersistResult {\n meta?: MetaState;\n metadataFailed: boolean;\n}\n\nexport async function runExtractionPostPersist(\n options: ExtractionPostPersistOptions,\n): Promise<ExtractionPostPersistResult> {\n let metadataFailed = false;\n let deadlineError: Error | undefined;\n const recordFailure = (stage: string, error: unknown): void => {\n metadataFailed = true;\n if (options.isDeadlineError(error) && deadlineError === undefined) {\n deadlineError = error as Error;\n }\n log.warn(`runExtraction: ${stage} failed after durable persistence; continuing with buffer clear`, error);\n };\n let meta = options.meta;\n try {\n meta ??= (await options.runDeadlineAware(\n () => options.storage.loadMeta(),\n \"during_post_persist_load_meta\",\n false,\n )) as MetaState;\n } catch (error) {\n recordFailure(\"metadata load\", error);\n }\n if (meta && options.extractionFingerprint && options.shouldPersistProcessedFingerprint) {\n try {\n await options.runDeadlineAware(\n () => options.recordProcessedExtractionFingerprint(options.storage, options.extractionFingerprint!, meta!),\n \"during_post_persist_fingerprint\",\n false,\n );\n } catch (error) {\n recordFailure(\"processed fingerprint\", error);\n }\n }\n if (meta) {\n meta.extractionCount += 1;\n meta.lastExtractionAt = new Date().toISOString();\n meta.totalMemories += Array.isArray(options.result.facts) ? options.result.facts.length : 0;\n meta.totalEntities += Array.isArray(options.result.entities) ? options.result.entities.length : 0;\n try {\n await options.runDeadlineAware(\n () => options.storage.saveMeta(meta!),\n \"during_post_persist_meta_save\",\n false,\n );\n } catch (error) {\n recordFailure(\"metadata save\", error);\n }\n }\n const runBestEffort = options.runPostPersistBestEffort;\n await runExtractionDeferRetention(runBestEffort, {\n clearBufferAfterExtraction: options.clearBufferAfterExtraction,\n extractionJudgeShadow: options.extractionJudgeShadow,\n getDeferredCount: options.getDeferredCount,\n normalizedTurns: options.normalizedTurns,\n bufferKey: options.bufferKey,\n retainDeferredTurns: options.retainDeferredTurns,\n });\n await runBestEffort(\n \"during_buffer_clear\",\n () => options.clearBuffer({ ignoreAbort: true }),\n {\n ignoreAbort: true,\n ignoreDeadline: options.failOnExtractionFailure,\n propagateErrors: options.failOnExtractionFailure,\n },\n );\n if (options.failOnExtractionFailure && typeof options.deadlineMs === \"number\" && Date.now() >= options.deadlineMs) {\n throw options.createDeadlineError(\"during_buffer_clear\");\n }\n if (deadlineError) throw deadlineError;\n return { meta, metadataFailed };\n}\n\n/**\n * Record a processed fingerprint when applicable and advance the extraction\n * liveness watermark only for a provider-backed empty success. Fingerprinted\n * imports treat the metadata save as load-bearing dedupe state; normal live\n * runs treat the stamp as diagnostic and fail open. Deadline errors always\n * propagate.\n */\nexport async function commitEmptySuccessExtraction(args: {\n storage: StorageManager;\n meta: MetaState | null;\n extractionFingerprint: string | null;\n shouldPersistProcessedFingerprint: boolean;\n hasExtractionFailure: boolean;\n extractionSkippedReason: ExtractionResult[\"extractionSkippedReason\"];\n recordProcessedExtractionFingerprint: (storage: StorageManager, fingerprint: string, meta: MetaState) => Promise<unknown>;\n runDeadlineAware: <T>(operation: () => Promise<T>, phase: string, clearTimerOnError?: boolean) => Promise<T>;\n isDeadlineError: (error: unknown) => boolean;\n}): Promise<void> {\n const {\n storage,\n meta: existingMeta,\n extractionFingerprint,\n shouldPersistProcessedFingerprint,\n hasExtractionFailure,\n extractionSkippedReason,\n recordProcessedExtractionFingerprint,\n runDeadlineAware,\n isDeadlineError,\n } = args;\n const shouldRecordFingerprint =\n extractionFingerprint !== null &&\n shouldPersistProcessedFingerprint &&\n !hasExtractionFailure;\n const shouldStampLiveness =\n !hasExtractionFailure && extractionSkippedReason === undefined;\n if (!shouldRecordFingerprint && !shouldStampLiveness) return;\n\n const metadataFailureIsFatal = shouldRecordFingerprint;\n try {\n const meta =\n existingMeta ??\n await runDeadlineAware(\n () => storage.loadMeta(),\n \"during_empty_success_liveness_load\",\n false,\n );\n if (shouldRecordFingerprint) {\n await recordProcessedExtractionFingerprint(storage, extractionFingerprint, meta);\n }\n if (shouldStampLiveness) {\n meta.extractionCount += 1;\n meta.lastExtractionAt = new Date().toISOString();\n }\n await runDeadlineAware(\n () => storage.saveMeta(meta),\n \"during_empty_success_liveness_save\",\n false,\n );\n } catch (err) {\n if (isDeadlineError(err)) throw err;\n if (metadataFailureIsFatal) throw err;\n log.warn(\"runExtraction: failed to stamp empty-success liveness watermark (non-fatal)\", err);\n }\n}\n","/**\n * Extraction-run coordinator — extracted from the orchestrator\n * (issue #1526, seam 15).\n *\n * Owns the buffer→extract→persist pipeline orchestration:\n * - `runExtraction` — the main extraction pipeline coordinator\n * (normalize → threshold gate → namespace resolve → dedupe\n * fingerprint → extract → validate → persist → post-write hooks)\n * - dedupe support: `shouldQueueExtraction`,\n * `buildExtractionFingerprint`, `normalizeExtractionFingerprintTurns`\n * - `recordProcessedExtractionFingerprint` — meta recording\n *\n * The orchestrator constructs one instance and delegates the extraction\n * run to it. `persistExtraction`, `maybeCapturePassiveCorrections`, and\n * all post-write side-effect helpers arrive as injectable delegates so\n * the orchestrator keeps ownership of cross-subsystem routing and the\n * behavior-critical chokepoints it hosts.\n *\n * Behavior-preserving move from orchestrator.ts. No logic changes — the\n * orchestrator keeps thin delegating methods so existing call sites\n * and tests that exercise the public API continue to work.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { StorageManager } from \"../index.js\";\nimport { NamespaceStorageRouter } from \"../namespaces/storage.js\";\nimport type { ExtractionEngine } from \"../extraction.js\";\nimport type { SmartBuffer } from \"../buffer.js\";\nimport type { ThreadingManager } from \"../threading.js\";\nimport type { BoxBuilder } from \"../boxes.js\";\nimport { resolvePrincipal, defaultNamespaceForPrincipal } from \"../namespaces/principal.js\";\nimport { resolveScopeProfilePlan, type ResolvedScopeProfilePlan } from \"../namespaces/scope-profiles.js\";\nimport { resolveCodingNamespaceOverlay } from \"../coding/coding-namespace.js\";\nimport type { CodingContext } from \"../types.js\";\nimport { deriveExtractionOriginContext, type ExtractionSourceContext } from \"./extraction-origin-context.js\";\nimport {\n resolvePresentationCapabilities,\n resolveMemoryLifecycleCapabilities,\n type GraphConstructionCapabilitySet,\n type MemoryLifecycleCapabilitySet,\n} from \"../capabilities.js\";\nimport { parseFlexibleIsoTimestamp } from \"../utils/iso-timestamp.js\";\nimport { tagPersistedMemories } from \"../location/tagging.js\";\nimport { log } from \"../logger.js\";\nimport { emitExtractionLlmFailureError, recordExtractionLlmFailure, recordExtractionLlmSuccess } from \"../extraction-llm-health.js\";\nimport type { PluginConfig, BufferTurn, ExtractionResult, MetaState, ExtractionFailureClass } from \"../types.js\";\nimport type { TierMigrationCycleSummary } from \"../recall-state.js\";\n\nexport interface ExtractionRunResult {\n status: \"completed\" | \"skipped\";\n reason?: string;\n persistedCount: number;\n durableOutputCount: number;\n postPersistMetadataFailed?: boolean;\n}\n\nexport class ExtractionDeadlineError extends Error {\n readonly stage: string;\n\n constructor(stage: string) {\n super(`replay extraction deadline exceeded (${stage})`);\n this.name = \"ExtractionDeadlineError\";\n this.stage = stage;\n }\n}\n\n/** Dependencies injected by the orchestrator. All stable references or\n * live accessors — lazy getters for anything tests reassign\n * post-construction (buffer, extraction, storageRouter, threading). */\nexport interface ExtractionRunCoordinatorDeps {\n config: PluginConfig;\n getBuffer: () => SmartBuffer;\n getExtraction: () => Pick<ExtractionEngine, \"extract\">;\n getStorageRouter: () => Pick<NamespaceStorageRouter, \"storageFor\">;\n getThreading: () => Pick<ThreadingManager, \"processTurn\" | \"updateThreadTitle\">;\n\n persistExtraction: (\n result: ExtractionResult,\n storage: StorageManager,\n threadIdForExtraction?: string | null,\n sourceContext?: ExtractionSourceContext,\n baseNamespace?: string,\n scopeProfileWritePlan?: ResolvedScopeProfilePlan | null,\n sourceText?: string,\n graphCaps?: GraphConstructionCapabilitySet,\n lifecycleCaps?: MemoryLifecycleCapabilitySet\n ) => Promise<{ persistedIds: string[]; memoryPathById: Map<string, string> }>;\n\n maybeCapturePassiveCorrections: (\n turns: readonly BufferTurn[],\n opts: {\n sessionKey: string;\n principal?: string;\n namespace: string;\n bufferKey: string;\n isLiveSession: boolean;\n abortSignal?: AbortSignal;\n }\n ) => Promise<void>;\n\n resolveSelfNamespace: (sessionKey?: string) => string;\n getCodingContextForSession: (sessionKey: string) => CodingContext | null;\n applyCodingNamespaceOverlay: (sessionKey: string, namespace: string) => string;\n boxBuilderFor: (storage: StorageManager) => Pick<BoxBuilder, \"onExtraction\">;\n appendPersistedThreadEpisodes: (threadId: string, ids: string[]) => Promise<void>;\n maybeScheduleConsolidation: (nonZero: boolean) => void;\n requestQmdMaintenance: () => void;\n runTierMigrationCycle: (\n storage: StorageManager,\n trigger: \"extraction\" | \"maintenance\" | \"manual\",\n options?: {\n dryRun?: boolean;\n limitOverride?: number;\n force?: boolean;\n }\n ) => Promise<TierMigrationCycleSummary>;\n getLastPersistExtractionDeferredCount: () => number;\n recordProcessedExtractionFingerprint: (\n storage: StorageManager,\n fingerprint: string,\n preloadedMeta?: Awaited<ReturnType<StorageManager[\"loadMeta\"]>>\n ) => Promise<void>;\n}\n\n// Recall/extraction abort helpers live in orchestrator-helpers.ts since\n// #1526 seam 25; this file previously carried duplicate copies\n// (AGENTS.md rule 8 — no duplicated helpers).\nimport { raceRecallAbort, throwIfRecallAborted } from \"./orchestrator-helpers.js\";\nimport {\n capExtractionRetryStateEntries,\n combineExtractionAbortSignals,\n commitEmptySuccessExtraction,\n computeExtractionRetryNextEligibleMs,\n deriveSourceConnector,\n deriveTopicsFromExtraction,\n runExtractionPostPersist,\n runExtractionPostPersistBestEffort,\n EXTRACTION_RETRY_STATE_MAX_ENTRIES,\n} from \"./extraction-run-helpers.js\";\nimport type { ExtractionResilienceStatus, ExtractionRetryStateEntry } from \"./extraction-run-helpers.js\";\n\nexport {\n capExtractionRetryStateEntries,\n computeExtractionRetryNextEligibleMs,\n deriveSourceConnector,\n deriveTopicsFromExtraction,\n};\nexport type { ExtractionResilienceStatus };\n\n/**\n * Coordinates the extraction run pipeline. Holds the dedupe fingerprint\n * cache (`recentExtractionFingerprints`) and delegates all side effects\n * to injected orchestrator methods.\n */\nexport class ExtractionRunCoordinator {\n private readonly recentExtractionFingerprints = new Map<string, number>();\n // Per-fingerprint extraction retry/backoff state (extraction hot-loop\n // hardening). Outer key = namespace, inner key = fingerprint. Hydrated lazily\n // from namespace-scoped meta so the hot path is a Map lookup; persisted back\n // to meta on change (restart-safe, cross-process coherent via the shared\n // meta.json). The breaker below is intentionally per-process in-memory\n // (mirrors local-llm.cooldownUntilMs); a restart clears it but the persisted\n // backoff still throttles, so a restart can't resurrect the hot loop.\n private readonly extractionRetryState = new Map<string, Map<string, ExtractionRetryStateEntry>>();\n private readonly hydratedRetryNamespaces = new Set<string>();\n private providerBreaker: {\n consecutiveFailures: number;\n openUntilMs: number;\n state: \"closed\" | \"open\" | \"half_open\";\n lastReason: string;\n } = { consecutiveFailures: 0, openUntilMs: 0, state: \"closed\", lastReason: \"\" };\n\n constructor(private readonly deps: ExtractionRunCoordinatorDeps) {}\n\n private get config(): PluginConfig {\n return this.deps.config;\n }\n\n // -------------------------------------------------------------------------\n // Source-valid-at helpers (moved from orchestrator.ts module scope)\n // -------------------------------------------------------------------------\n\n private latestSourceValidAtFromTurns(turns: readonly BufferTurn[]): string | undefined {\n let latestMs: number | null = null;\n for (const turn of turns) {\n if (turn.extractionContextOnly === true) continue;\n if (typeof turn.sourceValidAt !== \"string\") continue;\n const parsed = parseFlexibleIsoTimestamp(turn.sourceValidAt.trim());\n if (parsed === null) continue;\n if (latestMs === null || parsed > latestMs) {\n latestMs = parsed;\n }\n }\n return latestMs === null ? undefined : new Date(latestMs).toISOString();\n }\n\n // -------------------------------------------------------------------------\n // Extraction dedupe fingerprint helpers\n // -------------------------------------------------------------------------\n\n normalizeExtractionFingerprintTurns(turns: BufferTurn[]): string[] {\n if (!Array.isArray(turns) || turns.length === 0) return [];\n return turns\n .filter((turn) => turn.role === \"user\" || turn.role === \"assistant\")\n .map((turn) => {\n if (typeof turn.turnFingerprint === \"string\" && turn.turnFingerprint.length > 0) {\n return `fp:${turn.turnFingerprint}`;\n }\n return `${turn.role}:${(turn.content ?? \"\").replace(/\\s+/g, \" \").trim().slice(0, this.config.extractionMaxTurnChars)}`;\n })\n .filter((value) => value.length > 0);\n }\n\n buildExtractionFingerprint(turns: BufferTurn[], bufferKey: string): string | null {\n const normalized = this.normalizeExtractionFingerprintTurns(turns).join(\"\\n\");\n if (!normalized) return null;\n return createHash(\"sha256\").update(`${bufferKey}\\n${normalized}`).digest(\"hex\");\n }\n\n shouldQueueExtraction(turns: BufferTurn[], options: { commit?: boolean; bufferKey?: string } = {}): boolean {\n const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);\n if (!lifecycleCaps.extractionDedupe) return true;\n if (!Array.isArray(turns) || turns.length === 0) return false;\n\n const bufferKey = options.bufferKey ?? turns[0]?.sessionKey ?? \"default\";\n const fingerprint = this.buildExtractionFingerprint(turns, bufferKey);\n if (!fingerprint) return false;\n const now = Date.now();\n const seenAt = this.recentExtractionFingerprints.get(fingerprint);\n if (seenAt && now - seenAt < this.config.extractionDedupeWindowMs) {\n log.debug(\"extraction dedupe: skipped duplicate buffered turn set\");\n return false;\n }\n\n if (options.commit !== false) {\n this.recentExtractionFingerprints.set(fingerprint, now);\n }\n // Keep this cache bounded to avoid unbounded growth.\n if (options.commit !== false && this.recentExtractionFingerprints.size > 200) {\n const entries = Array.from(this.recentExtractionFingerprints.entries()).sort((a, b) => a[1] - b[1]);\n for (const [key] of entries.slice(0, entries.length - 200)) {\n this.recentExtractionFingerprints.delete(key);\n }\n }\n\n return true;\n }\n\n // -------------------------------------------------------------------------\n // Processed-fingerprint recording\n // -------------------------------------------------------------------------\n\n async recordProcessedExtractionFingerprint(\n storage: StorageManager,\n fingerprint: string,\n preloadedMeta?: Awaited<ReturnType<StorageManager[\"loadMeta\"]>>\n ): Promise<void> {\n const meta = preloadedMeta ?? (await storage.loadMeta());\n const observedAt = new Date().toISOString();\n const seen = new Map(\n (meta.processedExtractionFingerprints ?? []).map((entry) => [entry.fingerprint, entry.observedAt])\n );\n seen.set(fingerprint, observedAt);\n meta.processedExtractionFingerprints = Array.from(seen.entries())\n .map(([value, at]) => ({ fingerprint: value, observedAt: at }))\n .sort((left, right) => left.observedAt.localeCompare(right.observedAt))\n .slice(-500);\n if (!preloadedMeta) {\n await storage.saveMeta(meta);\n }\n }\n\n // -------------------------------------------------------------------------\n // Extraction retry/backoff + circuit breaker (extraction hot-loop hardening)\n // -------------------------------------------------------------------------\n\n /** Lazily hydrate the in-memory retry-state mirror for a namespace from meta.\n * One-time per namespace per coordinator; the hot path is then a Map lookup.\n * Cross-process coherence: a fresh coordinator hydrates from the shared\n * meta.json, so a failure persisted by another process is honored here. */\n private hydrateRetryStateFromMeta(namespace: string, meta: MetaState): void {\n if (this.hydratedRetryNamespaces.has(namespace)) return;\n this.hydratedRetryNamespaces.add(namespace);\n const nsMap = this.extractionRetryState.get(namespace) ?? new Map<string, ExtractionRetryStateEntry>();\n for (const entry of meta.extractionRetryState ?? []) {\n const nextEligibleAtMs = Date.parse(entry.nextEligibleAt);\n const firstFailedAtMs = Date.parse(entry.firstFailedAt);\n nsMap.set(entry.fingerprint, {\n attempts: entry.attempts,\n nextEligibleAtMs: Number.isFinite(nextEligibleAtMs) ? nextEligibleAtMs : 0,\n firstFailedAtMs: Number.isFinite(firstFailedAtMs) ? firstFailedAtMs : Date.now(),\n lastFailureClass: entry.lastFailureClass,\n });\n }\n this.extractionRetryState.set(namespace, nsMap);\n }\n\n private logBreakerTransition(state: \"closed\" | \"open\" | \"half_open\"): void {\n // Once per state change (not per attempt) — AGENTS.md observability rule.\n log.warn(\"extraction: provider circuit breaker transition\", {\n state,\n consecutiveFailures: this.providerBreaker.consecutiveFailures,\n lastReason: this.providerBreaker.lastReason,\n openUntilMs: this.providerBreaker.openUntilMs,\n });\n }\n\n /** True while the breaker suppresses non-forced attempts. Flips an expired\n * `open` breaker to `half_open`, which allows exactly one probe (the next\n * non-forced attempt) because the extraction queue is a serial drain. */\n private isProviderBreakerOpen(now: number): boolean {\n if (now < this.providerBreaker.openUntilMs) return true;\n if (this.providerBreaker.state === \"open\") {\n this.providerBreaker.state = \"half_open\";\n this.logBreakerTransition(\"half_open\");\n }\n return false;\n }\n\n private onProviderFailure(cls: ExtractionFailureClass, now: number): void {\n this.providerBreaker.consecutiveFailures += 1;\n const trip =\n cls === \"auth_config\" ||\n // A failed half-open probe re-opens immediately (standard breaker\n // semantics). Without this, an auth_config-opened breaker (which trips\n // below the threshold) would get stuck half_open after a transient\n // probe failure and stop suppressing (cursor review).\n this.providerBreaker.state === \"half_open\" ||\n this.providerBreaker.consecutiveFailures >= this.config.extractionBreakerFailureThreshold;\n if (!trip) return;\n const cooldown =\n cls === \"auth_config\"\n ? this.config.extractionBreakerAuthCooldownMs\n : this.config.extractionBreakerCooldownMs;\n this.providerBreaker.openUntilMs = now + Math.max(0, cooldown);\n this.providerBreaker.lastReason = cls;\n if (this.providerBreaker.state !== \"open\") {\n this.providerBreaker.state = \"open\";\n this.logBreakerTransition(\"open\");\n }\n }\n\n private resetProviderBreakerOnSuccess(): void {\n const wasDisturbed =\n this.providerBreaker.state !== \"closed\" ||\n this.providerBreaker.consecutiveFailures > 0 ||\n this.providerBreaker.openUntilMs > 0;\n if (!wasDisturbed) return;\n const wasOpen = this.providerBreaker.state !== \"closed\";\n this.providerBreaker.consecutiveFailures = 0;\n this.providerBreaker.openUntilMs = 0;\n this.providerBreaker.lastReason = \"\";\n this.providerBreaker.state = \"closed\";\n if (wasOpen) this.logBreakerTransition(\"closed\");\n }\n\n /** Rebuild `meta.extractionRetryState` from the in-memory namespace mirror,\n * bounded to the newest entries, and prune the mirror to match. */\n private persistRetryStateToMeta(namespace: string, meta: MetaState): void {\n const nsMap = this.extractionRetryState.get(namespace);\n const entries = nsMap\n ? Array.from(nsMap.entries()).map(([fingerprint, st]) => ({\n fingerprint,\n attempts: st.attempts,\n nextEligibleAt: new Date(st.nextEligibleAtMs).toISOString(),\n firstFailedAt: new Date(st.firstFailedAtMs).toISOString(),\n lastFailureClass: st.lastFailureClass,\n }))\n : [];\n const capped = capExtractionRetryStateEntries(entries, EXTRACTION_RETRY_STATE_MAX_ENTRIES);\n meta.extractionRetryState = capped;\n if (nsMap && capped.length < nsMap.size) {\n const kept = new Set(capped.map((e) => e.fingerprint));\n for (const key of Array.from(nsMap.keys())) {\n if (!kept.has(key)) nsMap.delete(key);\n }\n }\n }\n /** Record an extraction failure into per-fingerprint backoff state + breaker,\n * persisting to namespace meta. Fail-open: never throws upward. */\n private async recordExtractionFailure(\n storage: StorageManager,\n namespace: string,\n fingerprint: string,\n cls: ExtractionFailureClass,\n meta: MetaState,\n now: number,\n ): Promise<void> {\n try {\n this.hydrateRetryStateFromMeta(namespace, meta);\n const nsMap = this.extractionRetryState.get(namespace) ?? new Map<string, ExtractionRetryStateEntry>();\n const prev = nsMap.get(fingerprint);\n const attempts = (prev?.attempts ?? 0) + 1;\n const firstFailedAtMs = prev?.firstFailedAtMs ?? now;\n let nextEligibleAtMs: number;\n if (cls === \"parse_empty\" && attempts > this.config.extractionParseEmptyMaxAttempts) {\n // parse_empty exhausted its attempt budget → long-park (still never\n // marked processed). A dead gateway also yields unparseable output.\n nextEligibleAtMs = now + Math.max(0, this.config.extractionRetryMaxBackoffMs);\n } else {\n nextEligibleAtMs = computeExtractionRetryNextEligibleMs(\n attempts,\n this.config.extractionRetryScheduleMs,\n this.config.extractionRetryMaxBackoffMs,\n this.config.extractionRetryJitterRatio,\n now,\n );\n }\n nsMap.set(fingerprint, { attempts, nextEligibleAtMs, firstFailedAtMs, lastFailureClass: cls });\n this.extractionRetryState.set(namespace, nsMap);\n this.onProviderFailure(cls, now);\n this.persistRetryStateToMeta(namespace, meta);\n await storage.saveMeta(meta);\n } catch (err) {\n // Fail-open: recording failure state must never crash observe/recall.\n log.warn(\"runExtraction: failed to record extraction retry state (non-fatal)\", err);\n }\n }\n\n /** Delete a fingerprint's retry entry from the mirror + meta. Returns whether\n * meta changed (so the caller can decide to persist). Does not touch the\n * breaker — callers pair this with `resetProviderBreakerOnSuccess`. */\n private clearExtractionRetryEntry(namespace: string, fingerprint: string, meta: MetaState): boolean {\n const nsMap = this.extractionRetryState.get(namespace);\n if (!nsMap?.has(fingerprint)) return false;\n nsMap.delete(fingerprint);\n this.persistRetryStateToMeta(namespace, meta);\n return true;\n }\n\n /** Live in-process resilience snapshot (breaker + backoff population). */\n getExtractionResilienceStatus(): ExtractionResilienceStatus {\n let backoffFingerprintCount = 0;\n for (const nsMap of this.extractionRetryState.values()) {\n backoffFingerprintCount += nsMap.size;\n }\n return {\n breaker: {\n state: this.providerBreaker.state,\n openUntilMs: this.providerBreaker.openUntilMs,\n consecutiveFailures: this.providerBreaker.consecutiveFailures,\n lastReason: this.providerBreaker.lastReason,\n },\n backoffFingerprintCount,\n };\n }\n\n // -------------------------------------------------------------------------\n // Main extraction pipeline\n // -------------------------------------------------------------------------\n\n async runExtraction(\n turns: BufferTurn[],\n options: {\n clearBufferAfterExtraction?: boolean;\n clearMatchingTurns?: boolean;\n skipCharThreshold?: boolean;\n skipUserTurnThreshold?: boolean;\n deadlineMs?: number;\n bufferKey?: string;\n abortSignal?: AbortSignal;\n failOnExtractionFailure?: boolean;\n /**\n * Called immediately after durable extraction persistence returns, before\n * any metadata, retention, or helper work. Lifecycle callers use this\n * as the write-rate-limit commit boundary.\n */\n onDurableCommit?: () => void;\n /**\n * Explicit namespace override for the write path (#460). When set, this\n * is the namespace where extraction writes go instead of the one derived\n * from `defaultNamespaceForPrincipal(resolvePrincipal(sessionKey))`.\n * The resolved `principal` is still threaded into memory metadata for\n * provenance; only the storage target is overridden.\n */\n writeNamespaceOverride?: string;\n /**\n * Pin the provenance principal instead of deriving it from\n * `resolvePrincipal(sessionKey)`. When set, this is the identity an\n * access surface already authenticated; used so observed-turn provenance\n * is correct even though `turn.sessionKey` is the original key.\n */\n principalOverride?: string;\n /**\n * Access scope resolution may already have selected a profile while the\n * session context remains intentionally unbound. Reuse that plan for\n * persistence instead of deriving it from shared session state.\n */\n scopeProfileWritePlan?: ResolvedScopeProfilePlan | null;\n /**\n * Force the extractor call, bypassing the retry-backoff / circuit-breaker\n * gate (extraction hot-loop hardening). Set by explicit flush paths.\n */\n forceExtractionAttempt?: boolean;\n } = {}\n ): Promise<ExtractionRunResult> {\n log.debug(`running extraction on ${turns.length} turns`);\n const clearBufferAfterExtraction = options.clearBufferAfterExtraction ?? true;\n const clearMatchingTurns = options.clearMatchingTurns === true;\n const skipCharThreshold = options.skipCharThreshold ?? false;\n const skipUserTurnThreshold = options.skipUserTurnThreshold ?? false;\n const deadlineMs =\n typeof options.deadlineMs === \"number\" && Number.isFinite(options.deadlineMs) ? options.deadlineMs : undefined;\n const bufferKey = options.bufferKey ?? turns[0]?.sessionKey ?? \"default\";\n const throwIfDeadlineExceeded = (stage: string): void => {\n if (typeof deadlineMs === \"number\" && Date.now() >= deadlineMs) {\n throw new ExtractionDeadlineError(stage);\n }\n };\n const throwIfAborted = (stage: string): void => {\n throwIfRecallAborted(options.abortSignal, `extraction aborted (${stage})`);\n };\n const runPassiveCapture = async (\n captureTurns: readonly BufferTurn[],\n captureOptions: {\n sessionKey: string;\n principal?: string;\n namespace: string;\n bufferKey: string;\n isLiveSession: boolean;\n abortSignal?: AbortSignal;\n },\n ): Promise<void> => {\n const deadlineController =\n typeof deadlineMs === \"number\" && Number.isFinite(deadlineMs) ? new AbortController() : undefined;\n const captureSignal = combineExtractionAbortSignals(captureOptions.abortSignal ?? options.abortSignal,\n deadlineController);\n let deadlineTimer: NodeJS.Timeout | undefined;\n if (deadlineController && typeof deadlineMs === \"number\") {\n const scheduleDeadlineAbort = (): void => {\n const remainingMs = deadlineMs - Date.now();\n if (remainingMs <= 0) {\n deadlineController.abort();\n return;\n }\n deadlineTimer = setTimeout(\n () => {\n if (Date.now() >= deadlineMs) {\n deadlineController.abort();\n } else {\n scheduleDeadlineAbort();\n }\n },\n Math.min(remainingMs, 2_147_483_647),\n );\n };\n scheduleDeadlineAbort();\n }\n try {\n await raceRecallAbort(\n this.deps.maybeCapturePassiveCorrections(captureTurns, {\n ...captureOptions,\n ...(captureSignal ? { abortSignal: captureSignal } : {}),\n }),\n captureSignal,\n \"extraction aborted (during_passive_capture)\",\n );\n } catch (error) {\n if (typeof deadlineMs === \"number\" && Date.now() >= deadlineMs) {\n throw new ExtractionDeadlineError(\"during_passive_capture\");\n }\n throw error;\n } finally {\n if (deadlineTimer) clearTimeout(deadlineTimer);\n }\n };\n const clearBuffer = async (options?: { ignoreAbort?: boolean }) => {\n if (options?.ignoreAbort !== true) {\n throwIfDeadlineExceeded(\"before_clear_buffer\");\n throwIfAborted(\"before_clear_buffer\");\n }\n if (clearBufferAfterExtraction) {\n await this.deps\n .getBuffer()\n .clearAfterExtraction(bufferKey, turns, clearMatchingTurns ? { allowNonPrefix: true } : undefined);\n }\n };\n\n // Skip extraction for cron job sessions - these are system operations, not user conversations\n const sessionKey = turns[0]?.sessionKey ?? \"\";\n if (sessionKey.includes(\":cron:\")) {\n log.debug(`skipping extraction for cron session: ${sessionKey}`);\n await clearBuffer();\n return {\n status: \"skipped\",\n reason: \"cron_session\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n\n const normalizedTurns = turns\n .filter((t) => (t.role === \"user\" || t.role === \"assistant\") && typeof t.content === \"string\")\n .map((t) => ({\n ...t,\n content: t.content.trim().slice(0, this.config.extractionMaxTurnChars),\n }))\n .filter((t) => t.content.length > 0);\n const targetTurns = normalizedTurns.filter((turn) => turn.extractionContextOnly !== true);\n if (targetTurns.length === 0) {\n log.debug(\"skipping extraction: no non-context turns after normalization\");\n // Context-only turns may still contain corrections (review: \"context-only\n // turns skip capture\"). Scan normalizedTurns before clearing the buffer.\n if (normalizedTurns.length > 0) {\n throwIfDeadlineExceeded(\"before_context_only_capture\");\n const capturePrincipal =\n typeof options.principalOverride === \"string\" && options.principalOverride.length > 0\n ? options.principalOverride\n : resolvePrincipal(sessionKey, this.config);\n const captureNamespace =\n typeof options.writeNamespaceOverride === \"string\" && options.writeNamespaceOverride.length > 0\n ? options.writeNamespaceOverride\n : this.deps.resolveSelfNamespace(sessionKey);\n await runPassiveCapture(normalizedTurns as BufferTurn[], {\n sessionKey,\n principal: capturePrincipal,\n namespace: captureNamespace,\n bufferKey,\n isLiveSession: clearBufferAfterExtraction,\n });\n throwIfDeadlineExceeded(\"before_context_only_clear\");\n }\n await clearBuffer();\n return { status: \"skipped\", reason: \"empty_normalized_turns\", persistedCount: 0, durableOutputCount: 0 };\n }\n const sourceValidAt = this.latestSourceValidAtFromTurns(targetTurns);\n throwIfDeadlineExceeded(\"before_extract\");\n throwIfAborted(\"before_extract\");\n\n const userTurns = targetTurns.filter((t) => t.role === \"user\");\n const totalChars = targetTurns.reduce((sum, t) => sum + t.content.length, 0);\n const belowCharThreshold = totalChars < this.config.extractionMinChars;\n const belowUserTurnThreshold = !skipUserTurnThreshold && userTurns.length < this.config.extractionMinUserTurns;\n if ((!skipCharThreshold && belowCharThreshold) || belowUserTurnThreshold) {\n log.debug(`skipping extraction: below threshold (totalChars=${totalChars}, userTurns=${userTurns.length})`);\n // Passive correction capture runs even when extraction is skipped for\n // being below the char/user-turn threshold (review: \"skipped extraction\n // skips capture\"). A short correction like \"stop using Vim\" is under\n // extractionMinChars but is exactly the high-value case this feature\n // exists for. The write namespace resolves through the SAME scope-profile\n // plan as the full extraction path so corrections target the active\n // profile's write layer, not just resolveSelfNamespace (review:\n // \"below-threshold wrong namespace\"). The ACL in passiveCorrectionService\n // authorizes the actual plan/apply.\n {\n const capturePrincipal =\n typeof options.principalOverride === \"string\" && options.principalOverride.length > 0\n ? options.principalOverride\n : resolvePrincipal(sessionKey, this.config);\n const captureWO =\n typeof options.writeNamespaceOverride === \"string\" && options.writeNamespaceOverride.length > 0\n ? options.writeNamespaceOverride\n : undefined;\n const captureCodingCtx = sessionKey ? this.deps.getCodingContextForSession(sessionKey) : null;\n const captureCodingOv = resolveCodingNamespaceOverlay(\n captureCodingCtx,\n this.config.codingMode,\n this.config.defaultNamespace\n );\n const captureScopePlan = resolveScopeProfilePlan({\n config: this.config,\n principal: capturePrincipal,\n codingContext: captureCodingCtx,\n codingOverlay: captureCodingOv,\n });\n const captureNamespace =\n captureWO ?? captureScopePlan?.writeNamespace ?? this.deps.resolveSelfNamespace(sessionKey);\n await runPassiveCapture(normalizedTurns as BufferTurn[], {\n sessionKey,\n principal: capturePrincipal,\n namespace: captureNamespace,\n bufferKey,\n isLiveSession: clearBufferAfterExtraction,\n });\n }\n await clearBuffer();\n return {\n status: \"skipped\",\n reason: \"below_threshold\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n\n // Provenance principal honours the access-surface override (#1495 thread 1,\n // mirroring the recall path's `principalOverride`, issue #570 PR 4). Access\n // surfaces that authenticated the caller at the transport layer pass their\n // resolved principal so provenance uses the SAME identity the surface\n // authorized, instead of `resolvePrincipal(sessionKey)` — which on a\n // namespace-prefixed key would collapse to `default`. The ORIGINAL,\n // un-prefixed session key still drives threading.\n const principal =\n typeof options.principalOverride === \"string\" && options.principalOverride.length > 0\n ? options.principalOverride\n : resolvePrincipal(sessionKey, this.config);\n // Write path — explicit callers still win. Otherwise, an active hosted\n // scope profile owns the extraction write target so hook-captured turns land\n // in the same layer that profile recall searches. Without a profile, preserve\n // the existing coding-agent overlay behavior (issue #569).\n const explicitWriteNamespace =\n typeof options.writeNamespaceOverride === \"string\" && options.writeNamespaceOverride.length > 0\n ? options.writeNamespaceOverride\n : undefined;\n const codingContextForWrite = sessionKey ? this.deps.getCodingContextForSession(sessionKey) : null;\n const codingOverlayForWrite = resolveCodingNamespaceOverlay(\n codingContextForWrite,\n this.config.codingMode,\n this.config.defaultNamespace\n );\n const scopeProfileGatePlan =\n options.scopeProfileWritePlan !== undefined\n ? options.scopeProfileWritePlan\n : resolveScopeProfilePlan({\n config: this.config,\n principal,\n codingContext: codingContextForWrite,\n codingOverlay: codingOverlayForWrite,\n });\n const scopeProfileWritePlan =\n explicitWriteNamespace && options.scopeProfileWritePlan === undefined\n ? null\n : scopeProfileGatePlan;\n if (scopeProfileWritePlan) {\n const selectedLayer = scopeProfileWritePlan.layers.find((layer) => layer.id === scopeProfileWritePlan.writeLayer);\n const writeNamespaceReadable = scopeProfileWritePlan.readNamespaces.includes(\n scopeProfileWritePlan.writeNamespace\n );\n if (!selectedLayer?.writable || !writeNamespaceReadable) {\n log.warn(\n `runExtraction: skipping scope profile ${scopeProfileWritePlan.profileId} because write layer ${scopeProfileWritePlan.writeLayer} is not writable inside the profile read stack`\n );\n await clearBuffer();\n return {\n status: \"skipped\",\n reason: \"scope_profile_no_writable_layer\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n }\n const selfNamespace =\n explicitWriteNamespace ??\n scopeProfileWritePlan?.writeNamespace ??\n this.deps.applyCodingNamespaceOverlay(sessionKey, defaultNamespaceForPrincipal(principal, this.config));\n const extractionDeadlineController =\n typeof deadlineMs === \"number\" && Number.isFinite(deadlineMs)\n ? new AbortController()\n : undefined;\n const extractionAbortSignal = extractionDeadlineController\n ? options.abortSignal\n ? AbortSignal.any([options.abortSignal, extractionDeadlineController.signal])\n : extractionDeadlineController.signal\n : options.abortSignal;\n let extractionDeadlineTimer: NodeJS.Timeout | undefined;\n const clearExtractionDeadlineTimer = (): void => {\n if (extractionDeadlineTimer) {\n clearTimeout(extractionDeadlineTimer);\n extractionDeadlineTimer = undefined;\n }\n };\n const runDeadlineAware = async <T>(\n operation: () => Promise<T>,\n phase: string,\n clearTimerOnError = true,\n options: { ignoreAbort?: boolean; ignoreDeadline?: boolean } = {},\n ): Promise<T> => {\n const operationSignal = options.ignoreAbort ? undefined : extractionAbortSignal;\n try {\n if (!options.ignoreDeadline && typeof deadlineMs === \"number\" && Date.now() >= deadlineMs) {\n throwIfDeadlineExceeded(phase);\n }\n if (operationSignal?.aborted) {\n throwIfAborted(phase);\n }\n return await raceRecallAbort(\n operation(),\n operationSignal,\n `extraction aborted (${phase})`,\n );\n } catch (error) {\n if (clearTimerOnError) clearExtractionDeadlineTimer();\n if (!options.ignoreDeadline && typeof deadlineMs === \"number\" && Date.now() >= deadlineMs) {\n throw new ExtractionDeadlineError(phase);\n }\n throw error;\n }\n };\n if (extractionDeadlineController && typeof deadlineMs === \"number\") {\n const deadline = deadlineMs;\n const maxTimerDelayMs = 2_147_483_647;\n const scheduleDeadlineAbort = (): void => {\n const remainingMs = deadline - Date.now();\n if (remainingMs <= 0) {\n extractionDeadlineController.abort();\n return;\n }\n extractionDeadlineTimer = setTimeout(() => {\n if (Date.now() >= deadline) {\n extractionDeadlineController.abort();\n } else {\n scheduleDeadlineAbort();\n }\n }, Math.min(remainingMs, maxTimerDelayMs));\n };\n scheduleDeadlineAbort();\n }\n const storage = await runDeadlineAware(\n () => this.deps.getStorageRouter().storageFor(selfNamespace),\n \"during_storage\",\n );\n const shouldPersistProcessedFingerprint = targetTurns.some((turn) => turn.persistProcessedFingerprint === true);\n const extractionFingerprint = this.buildExtractionFingerprint(targetTurns, bufferKey);\n let meta = extractionFingerprint && shouldPersistProcessedFingerprint\n ? await runDeadlineAware(() => storage.loadMeta(), \"during_load_meta\")\n : null;\n if (\n extractionFingerprint &&\n shouldPersistProcessedFingerprint &&\n (meta?.processedExtractionFingerprints ?? []).some((entry) => entry.fingerprint === extractionFingerprint)\n ) {\n await runDeadlineAware(() => clearBuffer(), \"during_clear_buffer\");\n clearExtractionDeadlineTimer();\n return {\n status: \"skipped\",\n reason: \"processed_fingerprint\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n\n // Extraction retry/backoff + circuit-breaker gate (extraction hot-loop\n // hardening). Suppresses re-attempts of a recently-failed fingerprint and\n // short-circuits every fingerprint while the provider breaker is open, so a\n // failing LLM endpoint is not hammered once per observe. Forced flushes and\n // fail-closed callers bypass the gate (rule 18) but still record failures.\n // On a suppressed attempt the buffer is deliberately NOT cleared, so no\n // extractable turn is dropped — it is retried after cooldown/nextEligibleAt.\n const forcedExtractionAttempt =\n options.failOnExtractionFailure === true || options.forceExtractionAttempt === true;\n // Resolve the gate through the shared lifecycle capability plan (issue\n // #1523) rather than reading the raw config flag at each call site.\n const extractionRetryEnabled = resolveMemoryLifecycleCapabilities(this.config).extractionRetry;\n if (extractionFingerprint && !forcedExtractionAttempt && extractionRetryEnabled) {\n try {\n meta ??= await runDeadlineAware(() => storage.loadMeta(), \"during_retry_gate\");\n this.hydrateRetryStateFromMeta(selfNamespace, meta);\n const nowMs = Date.now();\n let suppressReason: \"provider_circuit_open\" | \"extraction_backoff\" | null = null;\n if (this.isProviderBreakerOpen(nowMs)) {\n suppressReason = \"provider_circuit_open\";\n } else {\n const st = this.extractionRetryState.get(selfNamespace)?.get(extractionFingerprint);\n if (st && nowMs < st.nextEligibleAtMs) suppressReason = \"extraction_backoff\";\n }\n if (suppressReason) {\n // Passive correction capture is local and must not be delayed by a\n // provider cooldown. It remains fail-open.\n try {\n await runDeadlineAware(\n () => runPassiveCapture(normalizedTurns as BufferTurn[], {\n sessionKey,\n principal,\n namespace: selfNamespace,\n bufferKey,\n isLiveSession: clearBufferAfterExtraction,\n }),\n \"during_passive_capture\",\n );\n } catch (captureErr) {\n if (captureErr instanceof ExtractionDeadlineError) throw captureErr;\n log.warn(\"runExtraction: passive correction capture failed on suppressed attempt (non-fatal)\", captureErr);\n }\n clearExtractionDeadlineTimer();\n return { status: \"skipped\", reason: suppressReason, persistedCount: 0, durableOutputCount: 0 };\n }\n } catch (err) {\n if (err instanceof ExtractionDeadlineError) throw err;\n // Fail-open: a gate error must never block extraction.\n log.warn(\"runExtraction: extraction retry gate check failed; proceeding (fail-open)\", err);\n }\n }\n\n // Pass existing entity names so the LLM can reuse them instead of inventing variants.\n const existingEntities = await runDeadlineAware(\n () => storage.listEntityNames(),\n \"during_list_entity_names\",\n );\n let result: ExtractionResult;\n try {\n result = await raceRecallAbort(\n this.deps.getExtraction().extract(normalizedTurns, existingEntities, extractionAbortSignal),\n extractionAbortSignal,\n \"extraction aborted (during_extract)\",\n );\n } catch (error) {\n clearExtractionDeadlineTimer();\n if (typeof deadlineMs === \"number\" && Date.now() >= deadlineMs) {\n throw new ExtractionDeadlineError(\"during_extract\");\n }\n throw error;\n }\n try {\n throwIfDeadlineExceeded(\"before_persist\");\n throwIfAborted(\"before_persist\");\n\n // Defensive: validate extraction result before processing. Explicit\n // fail-closed callers, such as flush-plan import, must not observe\n // malformed extractor output as a successful skip.\n if (!result) {\n log.warn(\"runExtraction: extraction returned null/undefined\");\n if (options.failOnExtractionFailure) {\n throw new Error(\"extraction failed: invalid_extraction_result\");\n }\n await clearBuffer();\n return {\n status: \"skipped\",\n reason: \"invalid_extraction_result\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n const invalidExtractionResultFields = [\n [\"facts\", result.facts],\n [\"entities\", result.entities],\n [\"questions\", result.questions],\n [\"profileUpdates\", result.profileUpdates],\n ]\n .filter(([, value]) => !Array.isArray(value))\n .map(([field]) => field);\n if (invalidExtractionResultFields.length > 0) {\n log.warn(\"runExtraction: extraction returned invalid collection fields\", {\n invalidFields: invalidExtractionResultFields,\n resultKeys: typeof result === \"object\" && result !== null ? Object.keys(result) : [],\n });\n if (options.failOnExtractionFailure) {\n throw new Error(\"extraction failed: invalid_extraction_result\");\n }\n await clearBuffer();\n return {\n status: \"skipped\",\n reason: \"invalid_extraction_result\",\n persistedCount: 0,\n durableOutputCount: 0,\n };\n }\n const extractionFailure =\n typeof result.extractionFailure === \"string\" && result.extractionFailure.trim().length > 0\n ? result.extractionFailure\n : undefined;\n // Issue #3140: surface extraction-LLM outages on /health and as an\n // ERROR-level deduplicated event instead of debug-only lines. Records on\n // every completed attempt so a later success clears the failure.\n if (extractionFailure) {\n recordExtractionLlmFailure(extractionFailure);\n emitExtractionLlmFailureError(extractionFailure, result.extractionFailureClass);\n } else if (result.extractionSkippedReason !== undefined) {\n // Prefiltered batch: no LLM was contacted, so we cannot confirm or deny\n // reachability. Do not clear the outage state.\n } else {\n recordExtractionLlmSuccess();\n }\n let recordedRetryFailure = false;\n // Record failure into backoff/breaker state, or heal on success. Runs for\n // every result path (empty and durable), before the fail-closed throw, so a\n // forced flush still records its failure (rule 18). Gated by\n // extractionRetryEnabled so a disabled feature restores prior behavior.\n if (extractionFingerprint && extractionRetryEnabled) {\n if (extractionFailure) {\n try {\n meta ??= await runDeadlineAware(() => storage.loadMeta(), \"during_retry_bookkeeping_load\", false);\n await runDeadlineAware(\n () =>\n this.recordExtractionFailure(\n storage,\n selfNamespace,\n extractionFingerprint,\n result.extractionFailureClass ?? \"provider_retryable\",\n meta!,\n Date.now(),\n ),\n \"during_retry_bookkeeping_save\",\n false,\n );\n // The buffer is retained below so the backoff gate can re-attempt\n // these turns after nextEligibleAt — clearing would orphan the\n // retry state.\n recordedRetryFailure = true;\n } catch (err) {\n if (err instanceof ExtractionDeadlineError) throw err;\n // Fail-open (codex review): retry bookkeeping is best-effort — a\n // corrupt or locked meta store must not reject the extraction task.\n // With no backoff recorded there is nothing to retain for, so the\n // pre-#1908 clear-buffer behavior applies below.\n log.warn(\"runExtraction: failed to load meta for retry bookkeeping (non-fatal)\", err);\n }\n } else if (result.extractionSkippedReason === undefined) {\n // Provider responded without failure → breaker heals; clear any\n // parked backoff for this fingerprint so it proceeds normally.\n this.resetProviderBreakerOnSuccess();\n try {\n meta ??= await runDeadlineAware(() => storage.loadMeta(), \"during_retry_bookkeeping_load\", false);\n // Hydrate the in-memory mirror from persisted meta before clearing.\n // A forced flush (forceExtractionAttempt) bypasses the retry gate and\n // never hydrated, so without this a stale persisted backoff entry\n // would survive a successful forced flush and keep blocking normal\n // extraction until the timer expired (cursor/codex review).\n this.hydrateRetryStateFromMeta(selfNamespace, meta);\n if (this.clearExtractionRetryEntry(selfNamespace, extractionFingerprint, meta)) {\n await runDeadlineAware(() => storage.saveMeta(meta!), \"during_retry_bookkeeping_save\", false);\n }\n } catch (err) {\n if (err instanceof ExtractionDeadlineError) throw err;\n log.warn(\"runExtraction: failed to persist cleared retry state (non-fatal)\", err);\n }\n }\n }\n if (options.failOnExtractionFailure && extractionFailure) {\n throw new Error(`extraction failed: ${extractionFailure}`);\n }\n if (\n result.facts.length === 0 &&\n result.entities.length === 0 &&\n result.questions.length === 0 &&\n result.profileUpdates.length === 0\n ) {\n log.debug(\"runExtraction: extraction produced no durable outputs; skipping persistence\");\n if (extractionFailure) {\n log.warn(\n \"runExtraction: extraction reported failure with no durable outputs; not marking fingerprint processed\",\n { extractionFailure }\n );\n }\n const emptySuccessCommit = {\n storage,\n meta,\n extractionFingerprint,\n shouldPersistProcessedFingerprint,\n hasExtractionFailure: Boolean(extractionFailure),\n extractionSkippedReason: result.extractionSkippedReason,\n recordProcessedExtractionFingerprint: this.deps.recordProcessedExtractionFingerprint,\n runDeadlineAware,\n isDeadlineError: (error: unknown) => error instanceof ExtractionDeadlineError,\n };\n if (shouldPersistProcessedFingerprint) {\n await commitEmptySuccessExtraction(emptySuccessCommit);\n }\n await runPassiveCapture(normalizedTurns as BufferTurn[], {\n sessionKey,\n principal,\n namespace: selfNamespace,\n bufferKey,\n isLiveSession: clearBufferAfterExtraction,\n });\n if (recordedRetryFailure) {\n log.debug(\"runExtraction: retaining buffer for backoff retry after failure\");\n } else {\n await clearBuffer();\n }\n if (!shouldPersistProcessedFingerprint) {\n await commitEmptySuccessExtraction(emptySuccessCommit);\n }\n return { status: \"skipped\", reason: \"empty_extraction_result\", persistedCount: 0, durableOutputCount: 0 };\n }\n\n let threadIdForExtraction: string | null = null;\n if (resolvePresentationCapabilities(this.config).threading && turns.length > 0) {\n const lastTurn = turns[turns.length - 1];\n try {\n threadIdForExtraction = await runDeadlineAware(\n () => this.deps.getThreading().processTurn(lastTurn, []),\n \"during_threading\",\n );\n } catch (err) {\n if (err instanceof ExtractionDeadlineError) throw err;\n // Fail-open: threading errors must not block memory persistence.\n log.warn(\"[threading] processTurn failed before persistence (non-fatal)\", err);\n }\n }\n const originContext = deriveExtractionOriginContext(targetTurns, result.sourceConnector);\n throwIfDeadlineExceeded(\"before_persist\");\n throwIfAborted(\"before_persist\");\n\n const { persistedIds } = await this.deps.persistExtraction(\n result,\n storage,\n threadIdForExtraction,\n { sessionKey, principal, validAt: sourceValidAt, ...originContext },\n // Pass the KNOWN base namespace (NHIdx) so the catalog write touch records the\n // real namespace rather than a guess decoded from the storage dir.\n selfNamespace,\n scopeProfileWritePlan,\n // Verbatim source turns for the faithfulness gate (#1576) so it can\n // locate a quote per fact when #1575 spans are absent.\n normalizedTurns\n .map((t) => t.content)\n .join(\"\\n\\n\")\n );\n const durableOutputCount =\n result.facts.length + result.entities.length + result.questions.length + result.profileUpdates.length;\n try {\n options.onDurableCommit?.();\n } catch (error) {\n log.warn(\"runExtraction: durable-commit callback failed\", error);\n }\n const runPostPersistBestEffort = runExtractionPostPersistBestEffort.bind(null, runDeadlineAware);\n await tagPersistedMemories(storage, persistedIds, this.config);\n const postPersist = await runExtractionPostPersist({\n result,\n storage,\n meta: meta ?? undefined,\n extractionFingerprint: extractionFingerprint ?? undefined,\n shouldPersistProcessedFingerprint,\n recordProcessedExtractionFingerprint: this.deps.recordProcessedExtractionFingerprint,\n runDeadlineAware,\n isDeadlineError: (error) => error instanceof ExtractionDeadlineError,\n createDeadlineError: (stage) => new ExtractionDeadlineError(stage),\n deadlineMs,\n clearBufferAfterExtraction,\n runPostPersistBestEffort,\n extractionJudgeShadow: this.config.extractionJudgeShadow,\n getDeferredCount: () => this.deps.getLastPersistExtractionDeferredCount(),\n normalizedTurns: normalizedTurns as BufferTurn[],\n bufferKey,\n retainDeferredTurns: (key, retainedTurns, max) =>\n this.deps.getBuffer().retainDeferredTurns(key, retainedTurns, max),\n clearBuffer,\n failOnExtractionFailure: options.failOnExtractionFailure === true,\n });\n meta = postPersist.meta ?? null;\n const postPersistMetadataFailed = postPersist.metadataFailed;\n\n // Passive correction capture (issue #1581) — detect corrections expressed\n // passively in the extracted turns and route to the Correction Contract.\n // Runs AFTER persistence + buffer clear so a capture failure never blocks\n // the extraction return. `clearBufferAfterExtraction` gates live-session\n // auto-apply (replay/import → queue-only).\n await runPostPersistBestEffort(\"during_post_persist_passive_capture\", () =>\n runPassiveCapture(normalizedTurns as BufferTurn[], {\n sessionKey,\n principal,\n namespace: selfNamespace,\n bufferKey,\n isLiveSession: clearBufferAfterExtraction,\n abortSignal: extractionAbortSignal,\n }),\n );\n\n // Build memory box from this extraction (v8.0 Phase 2A)\n // Topics are derived from the current extraction's facts and entities only —\n // not from readAllMemories() — so box topics accurately reflect the current\n // session window and the call is free of expensive full-corpus I/O.\n if (resolvePresentationCapabilities(this.config).memoryBoxes && persistedIds.length > 0) {\n const extractionTopics = deriveTopicsFromExtraction(result);\n // Derive episodic metadata from buffer turns (REMem-inspired)\n const firstUserTurn = turns.find((t) => t.role === \"user\");\n const boxGoal = firstUserTurn?.content?.slice(0, 100)?.trim() || undefined;\n await runPostPersistBestEffort(\"during_memory_box\", () =>\n this.deps.boxBuilderFor(storage).onExtraction({\n topics: extractionTopics,\n memoryIds: persistedIds,\n timestamp: new Date().toISOString(),\n goal: boxGoal,\n }),\n );\n }\n\n // Batch-append persisted IDs so non-fact memories (entities/questions) are\n // always attached to the thread. The helper excludes pending_review ids (#1635).\n if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction && persistedIds.length > 0) {\n await runPostPersistBestEffort(\"during_thread_episode_append\", () =>\n this.deps.appendPersistedThreadEpisodes(threadIdForExtraction, persistedIds),\n );\n }\n\n // Thread title update for the already-established thread context.\n if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction) {\n const conversationContent = turns.map((t) => t.content).join(\" \");\n await runPostPersistBestEffort(\"during_thread_title_update\", () =>\n this.deps.getThreading().updateThreadTitle(threadIdForExtraction, conversationContent),\n );\n }\n\n // Check if consolidation is needed (debounced + non-zero gated).\n const nonZeroExtraction = durableOutputCount > 0;\n try {\n // The increment of nonZeroExtractionsSinceConsolidation moved into\n // the scheduler with the rest of the cadence state (issue #1526 PR1).\n this.deps.maybeScheduleConsolidation(nonZeroExtraction);\n } catch (err) {\n log.warn(\"runExtraction: consolidation scheduling failed after persistence (non-fatal)\", err);\n }\n\n try {\n this.deps.requestQmdMaintenance();\n } catch (err) {\n log.warn(\"runExtraction: QMD maintenance scheduling failed after persistence (non-fatal)\", err);\n }\n\n await runPostPersistBestEffort(\"during_tier_migration\", () =>\n this.deps.runTierMigrationCycle(storage, \"extraction\"),\n );\n\n return {\n status: \"completed\",\n persistedCount: persistedIds.length,\n durableOutputCount,\n postPersistMetadataFailed,\n };\n } finally {\n clearExtractionDeadlineTimer();\n }\n }\n}\n","/**\n * Derive issue #1955 origin metadata at the extraction boundary.\n */\n\nimport { classifyOrigin, type OriginClass } from \"../security/origin-authority.js\";\nimport type { BufferTurn } from \"../types.js\";\n\nexport interface ExtractionSourceContext {\n sessionKey?: string;\n principal?: string;\n validAt?: string;\n sourceConnector?: string;\n turnRole?: \"user\" | \"assistant\" | \"tool\" | string;\n importAdapter?: string;\n originConflict?: boolean;\n}\n\n/** Derive one origin context from the turns that supplied an extraction. */\nexport function deriveExtractionOriginContext(\n targetTurns: readonly BufferTurn[],\n fallbackConnector?: string,\n): Pick<ExtractionSourceContext, \"turnRole\" | \"importAdapter\" | \"sourceConnector\" | \"originConflict\"> {\n const originRoles = new Set(targetTurns.map((turn) => turn.originRole ?? turn.role));\n const turnRole = originRoles.size === 1 ? [...originRoles][0] : undefined;\n const originConnectors = new Set(\n targetTurns\n .map((turn) => turn.sourceConnector)\n .filter((connector): connector is string => typeof connector === \"string\" && connector.length > 0),\n );\n // Mirror the pre-#1955 provenance rule: a batch only carries a connector\n // when EVERY turn is tagged with the same one — a mixed tagged+untagged\n // batch must stay connector-less (connector-provenance-lifecycle tests).\n const connector =\n targetTurns.length > 0 &&\n targetTurns.every(\n (turn) => typeof turn.sourceConnector === \"string\" && turn.sourceConnector.length > 0,\n ) &&\n originConnectors.size === 1\n ? [...originConnectors][0]\n : undefined;\n const originAdapters = new Set(\n targetTurns\n .map((turn) => turn.importProvenance?.sourceLabel)\n .filter((adapter): adapter is string => typeof adapter === \"string\" && adapter.length > 0),\n );\n // Conflict when identities disagree OR are only partially present: a batch\n // where some turns carry an import label (or connector) and others do not\n // must classify as unknown, never fall back to the uniform turn role\n // (#1955 review: mixed import labels previously became `user`).\n const everyTurnHasConnector =\n targetTurns.length > 0 &&\n targetTurns.every((turn) => typeof turn.sourceConnector === \"string\" && turn.sourceConnector.length > 0);\n const everyTurnHasAdapter =\n targetTurns.length > 0 &&\n targetTurns.every(\n (turn) => typeof turn.importProvenance?.sourceLabel === \"string\" && turn.importProvenance.sourceLabel.length > 0,\n );\n const originConflict =\n originConnectors.size > 1 ||\n originAdapters.size > 1 ||\n (originConnectors.size > 0 && originAdapters.size > 0) ||\n (originConnectors.size > 0 && !everyTurnHasConnector) ||\n (originAdapters.size > 0 && !everyTurnHasAdapter);\n const importAdapter =\n targetTurns.length > 0 &&\n targetTurns.every(\n (turn) =>\n typeof turn.importProvenance?.sourceLabel === \"string\" &&\n turn.importProvenance.sourceLabel.length > 0,\n ) &&\n originAdapters.size === 1\n ? [...originAdapters][0]\n : undefined;\n return {\n ...(turnRole ? { turnRole } : {}),\n ...(importAdapter ? { importAdapter } : {}),\n ...(originConflict ? { originConflict: true } : {}),\n sourceConnector: fallbackConnector ?? connector,\n };\n}\n\n/** Classify the persisted origin without reading config flags. */\nexport function classifyExtractionOrigin(sourceContext?: ExtractionSourceContext): OriginClass {\n if (sourceContext?.originConflict === true) return \"unknown\";\n return classifyOrigin({\n turnRole: sourceContext?.turnRole,\n connectorId: sourceContext?.sourceConnector,\n importAdapter: sourceContext?.importAdapter,\n });\n}\n"],"mappings":"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","names":["connector","options"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/maintenance/dreams-ledger.ts"],"sourcesContent":["/**\n * dreams-ledger.ts — per-phase telemetry for the Dreams consolidation pipeline.\n *\n * Every scheduled or manual phase run appends one JSONL entry to\n * <memoryDir>/state/dreams-ledger.jsonl\n *\n * The entry records which phase ran, how long it took, and a small\n * itemsProcessed counter so `remnic dreams status` can aggregate the last\n * 24-hour window without scanning the full memory corpus.\n *\n * This module is intentionally side-effect-free on import (no fs calls at\n * module load time) so tests can import it without touching the filesystem.\n */\n\nimport path from \"node:path\";\nimport { createReadStream, type ReadStream } from \"node:fs\";\nimport { appendFile, lstat, mkdir, readdir, readFile } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** The three named phases of the Dreams consolidation pipeline. */\nexport type DreamsPhase = \"lightSleep\" | \"rem\" | \"deepSleep\";\n\n/**\n * One entry written to the dreams ledger for every phase run.\n * Older entries that predate this field simply won't have it (no backfill).\n */\nexport interface DreamsLedgerEntry {\n /** Schema version — always 1 for entries written by this module. */\n schemaVersion: 1;\n /** ISO-8601 timestamp when the phase run started. */\n startedAt: string;\n /** ISO-8601 timestamp when the phase run completed. */\n completedAt: string;\n /** Wall-clock duration in milliseconds. */\n durationMs: number;\n /**\n * Which Dreams phase this entry represents.\n * - `lightSleep` — recent activity scoring + clustering\n * - `rem` — cross-session synthesis, supersession, consolidation\n * - `deepSleep` — tier migration, page-version snapshots, archive\n */\n phase: DreamsPhase;\n /** How many memory items were evaluated (scored / clustered / migrated). */\n itemsProcessed: number;\n /** Whether this was a dry-run (no writes committed). */\n dryRun: boolean;\n /**\n * How the phase was triggered.\n * - `scheduled` — invoked by the governance cron or orchestrator maintenance pass\n * - `manual` — invoked via `remnic dreams run`\n */\n trigger: \"scheduled\" | \"manual\";\n /** Free-form notes for debugging (optional, never required). */\n notes?: string;\n}\n\n/** Aggregated stats for a single phase over a time window. */\nexport interface DreamsPhaseStatus {\n phase: DreamsPhase;\n /** Number of phase runs in the window. */\n runCount: number;\n /** Total wall-clock milliseconds across all runs in the window. */\n totalDurationMs: number;\n /** Total items processed across all runs in the window. */\n totalItemsProcessed: number;\n /** ISO-8601 timestamp of the most recent completed run, or null if none. */\n lastRunAt: string | null;\n /** Duration of the most recent run in ms, or null if none. */\n lastDurationMs: number | null;\n}\n\n/** Shape returned by `getDreamsStatus` — the canonical telemetry response. */\nexport interface DreamsStatusResult {\n /** ISO-8601 start of the query window (inclusive). */\n windowStart: string;\n /** ISO-8601 end of the query window (exclusive). */\n windowEnd: string;\n /** Per-phase summaries, always present for all three phases. */\n phases: {\n lightSleep: DreamsPhaseStatus;\n rem: DreamsPhaseStatus;\n deepSleep: DreamsPhaseStatus;\n };\n}\n\n// ── Path helpers ──────────────────────────────────────────────────────────────\n\nexport function dreamsLedgerPath(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"dreams-ledger.jsonl\");\n}\n\n// ── Writer ────────────────────────────────────────────────────────────────────\n\n/**\n * Append a single entry to the dreams ledger.\n * Creates the file (and its parent directory) if needed.\n * Uses `appendFile` (O_APPEND) so concurrent callers never overwrite each other.\n */\nexport async function appendDreamsLedgerEntry(\n memoryDir: string,\n entry: DreamsLedgerEntry,\n): Promise<void> {\n const ledgerPath = dreamsLedgerPath(memoryDir);\n await mkdir(path.dirname(ledgerPath), { recursive: true });\n await appendFile(ledgerPath, JSON.stringify(entry) + \"\\n\", \"utf-8\");\n}\n\n// ── Reader ────────────────────────────────────────────────────────────────────\n\n/**\n * Parse and validate one ledger line into a `DreamsLedgerEntry`, or `null` when\n * blank/malformed/incomplete. Shared by the streaming reader and the windowed\n * status aggregator so both apply the identical fail-open guard.\n */\nfunction parseDreamsLedgerLine(line: string): DreamsLedgerEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n let parsed: Record<string, unknown>;\n try {\n const raw = JSON.parse(trimmed);\n if (typeof raw !== \"object\" || raw === null) return null;\n parsed = raw as Record<string, unknown>;\n } catch {\n return null; // Malformed JSON — skip (fail-open).\n }\n\n // Required, strongly-typed fields. A row missing or mistyping any of these is\n // not a usable telemetry entry.\n const phase = parsed.phase;\n if (phase !== \"lightSleep\" && phase !== \"rem\" && phase !== \"deepSleep\") return null;\n if (\n typeof parsed.startedAt !== \"string\" ||\n typeof parsed.completedAt !== \"string\" ||\n typeof parsed.durationMs !== \"number\" ||\n typeof parsed.itemsProcessed !== \"number\"\n ) {\n return null;\n }\n\n // Optional fields: absence is tolerated (older entries predate them, no\n // backfill), but a PRESENT value with the wrong type/value marks the row\n // malformed and skips it — never silently coerced to a default (issue #1910).\n if (\"schemaVersion\" in parsed && parsed.schemaVersion !== 1) return null;\n if (\"dryRun\" in parsed && typeof parsed.dryRun !== \"boolean\") return null;\n if (\"trigger\" in parsed && parsed.trigger !== \"manual\" && parsed.trigger !== \"scheduled\") {\n return null;\n }\n\n return {\n schemaVersion: 1,\n startedAt: parsed.startedAt,\n completedAt: parsed.completedAt,\n durationMs: parsed.durationMs,\n phase,\n itemsProcessed: parsed.itemsProcessed,\n dryRun: parsed.dryRun === true,\n trigger: parsed.trigger === \"manual\" ? \"manual\" : \"scheduled\",\n notes: typeof parsed.notes === \"string\" ? parsed.notes : undefined,\n };\n}\n\n/**\n * Stream the dreams ledger line-by-line. The ledger is plaintext `appendFile`\n * (never encrypted), so a raw line stream is safe at any size — heap stays\n * O(1) in the file, unlike the previous whole-file `readFile` + `split`.\n * Yields nothing when the ledger does not exist yet.\n */\nexport async function* streamDreamsLedgerLines(memoryDir: string): AsyncGenerator<string> {\n const ledgerPath = dreamsLedgerPath(memoryDir);\n let input: ReadStream;\n try {\n input = createReadStream(ledgerPath, { encoding: \"utf8\" });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n const rl = createInterface({ input, crlfDelay: Infinity });\n try {\n for await (const line of rl) yield line;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n } finally {\n // Close the readline interface AND destroy the underlying stream. When a\n // consumer stops early (e.g. `getDreamsStatus` breaks out), the generator's\n // `.return()` runs this finally; `rl.close()` alone leaves the file\n // descriptor open, so destroy the stream to release it (issue #1910).\n rl.close();\n input.destroy();\n }\n}\n\n/**\n * Parse all valid `DreamsLedgerEntry` rows from the ledger file. Thin wrapper\n * over the line stream so any caller that genuinely needs every row still\n * works; `getDreamsStatus` streams directly and filters in-window instead.\n * Malformed lines are silently skipped (resilience over strictness for\n * telemetry).\n */\nexport async function readDreamsLedgerEntries(memoryDir: string): Promise<DreamsLedgerEntry[]> {\n const entries: DreamsLedgerEntry[] = [];\n for await (const line of streamDreamsLedgerLines(memoryDir)) {\n const entry = parseDreamsLedgerLine(line);\n if (entry) entries.push(entry);\n }\n return entries;\n}\n\n// ── Aggregator ────────────────────────────────────────────────────────────────\n\nconst ALL_PHASES: DreamsPhase[] = [\"lightSleep\", \"rem\", \"deepSleep\"];\nconst MAX_WINDOW_HOURS = 24 * 365 * 100;\n\nexport function normalizeDreamsStatusWindowHours(value: unknown, fallback = 24): number {\n const raw = value === undefined || value === null ? fallback : value;\n if (\n typeof raw !== \"number\" ||\n !Number.isFinite(raw) ||\n !Number.isInteger(raw) ||\n raw < 1 ||\n raw > MAX_WINDOW_HOURS\n ) {\n throw new RangeError(`windowHours must be a positive integer no greater than ${MAX_WINDOW_HOURS}`);\n }\n return raw;\n}\n\n/**\n * Build per-phase 24-hour summary.\n *\n * @param memoryDir The memory directory (parent of `state/`).\n * @param windowHours How many hours to look back (default 24).\n * @param now Reference time (default `new Date()`).\n */\nexport async function getDreamsStatus(\n memoryDir: string,\n windowHours = 24,\n now: Date = new Date(),\n): Promise<DreamsStatusResult> {\n const boundedWindowHours = normalizeDreamsStatusWindowHours(windowHours);\n const nowMs = now.getTime();\n if (!Number.isFinite(nowMs)) {\n throw new RangeError(\"now must be a valid Date\");\n }\n const windowMs = boundedWindowHours * 60 * 60 * 1000;\n const windowStart = new Date(nowMs - windowMs);\n const windowEnd = now;\n if (!Number.isFinite(windowStart.getTime())) {\n throw new RangeError(\"windowHours produces an invalid status window\");\n }\n\n const windowStartMs = windowStart.getTime();\n const windowEndMs = windowEnd.getTime();\n\n const statusMap = new Map<DreamsPhase, DreamsPhaseStatus>();\n for (const phase of ALL_PHASES) {\n statusMap.set(phase, {\n phase,\n runCount: 0,\n totalDurationMs: 0,\n totalItemsProcessed: 0,\n lastRunAt: null,\n lastDurationMs: null,\n });\n }\n\n // Stream the ledger and aggregate every in-window row inline, so heap use is\n // O(number of phases) regardless of window or file size (issue #1910) — no\n // per-row array and no row cap, so a large in-window window is still counted\n // in full rather than truncated.\n for await (const line of streamDreamsLedgerLines(memoryDir)) {\n const entry = parseDreamsLedgerLine(line);\n if (!entry) continue;\n const ts = Date.parse(entry.completedAt);\n if (!Number.isFinite(ts) || ts < windowStartMs || ts >= windowEndMs) continue;\n const status = statusMap.get(entry.phase);\n if (!status) continue;\n status.runCount += 1;\n status.totalDurationMs += entry.durationMs;\n status.totalItemsProcessed += entry.itemsProcessed;\n // Track the most recent run (by completedAt).\n if (status.lastRunAt === null || ts > Date.parse(status.lastRunAt)) {\n status.lastRunAt = entry.completedAt;\n status.lastDurationMs = entry.durationMs;\n }\n }\n\n return {\n windowStart: windowStart.toISOString(),\n windowEnd: windowEnd.toISOString(),\n phases: {\n lightSleep: statusMap.get(\"lightSleep\")!,\n rem: statusMap.get(\"rem\")!,\n deepSleep: statusMap.get(\"deepSleep\")!,\n },\n };\n}\n\n// ── Phase runner (PR 4/4: manual invocation) ─────────────────────────────────\n\nexport interface DreamsRunOptions {\n memoryDir: string;\n phase: DreamsPhase;\n dryRun?: boolean;\n}\n\n/** Public result shape — identical to what HTTP, MCP, and CLI surfaces expose. */\nexport interface DreamsRunResult {\n phase: DreamsPhase;\n dryRun: boolean;\n durationMs: number;\n itemsProcessed: number;\n notes?: string;\n}\n\n/** Internal result shape returned by `runDreamsPhase` — includes the raw ledger entry. */\nexport interface DreamsRunResultInternal extends DreamsRunResult {\n ledgerEntry?: DreamsLedgerEntry;\n}\n\nexport async function recordDreamsPhaseRun(options: {\n memoryDir: string;\n phase: DreamsPhase;\n trigger: DreamsLedgerEntry[\"trigger\"];\n dryRun?: boolean;\n itemsProcessed: number;\n notes?: string;\n startedAt?: string;\n completedAt?: string;\n}): Promise<DreamsLedgerEntry> {\n const startedAt = options.startedAt ?? new Date().toISOString();\n const completedAt = options.completedAt ?? new Date().toISOString();\n const durationMs = Math.max(0, Date.parse(completedAt) - Date.parse(startedAt));\n const ledgerEntry: DreamsLedgerEntry = {\n schemaVersion: 1,\n startedAt,\n completedAt,\n durationMs: Number.isFinite(durationMs) ? durationMs : 0,\n phase: options.phase,\n itemsProcessed: Math.max(0, Math.floor(options.itemsProcessed)),\n dryRun: options.dryRun === true,\n trigger: options.trigger,\n notes: options.notes,\n };\n if (!ledgerEntry.dryRun) {\n await appendDreamsLedgerEntry(options.memoryDir, ledgerEntry);\n }\n return ledgerEntry;\n}\n\n/**\n * Manually invoke a single Dreams phase pass.\n *\n * For light sleep and REM: runs a lightweight scan against the observation\n * ledger and returns a count of items that would be (or were, if not dry-run)\n * processed. The actual heavy consolidation is long-running and orchestrator-\n * bound; the manual surface delegates to existing ledger utilities and returns\n * the phase telemetry so callers get the same shape as a scheduled run.\n *\n * For deep sleep: delegates to the memory governance run (shadow mode when\n * dryRun is true, apply mode otherwise).\n */\nexport async function runDreamsPhase(\n options: DreamsRunOptions,\n governanceRunner?: (opts: { memoryDir: string; dryRun: boolean }) => Promise<{ scannedMemories: number; appliedActionCount: number; notes?: string }>,\n phaseRunner?: (opts: { memoryDir: string; phase: Exclude<DreamsPhase, \"deepSleep\"> }) => Promise<{ itemsProcessed: number; notes?: string }>,\n): Promise<DreamsRunResultInternal> {\n const { memoryDir, phase, dryRun = false } = options;\n const startedAt = new Date().toISOString();\n\n let itemsProcessed = 0;\n let notes: string | undefined;\n\n if (phase === \"lightSleep\") {\n // Dry-run light sleep counts recent observation entries as a preview.\n // Live runs must be backed by the orchestrator phase runner so the command\n // never reports success without executing the phase.\n if (!dryRun) {\n if (!phaseRunner) {\n throw new Error(\"light-sleep manual runs require a phase runner\");\n }\n const result = await phaseRunner({ memoryDir, phase });\n itemsProcessed = Math.max(0, Math.floor(result.itemsProcessed));\n notes = result.notes ?? `scored ${itemsProcessed} memories`;\n } else {\n // Light sleep: count observation ledger entries from the last 24h as a\n // proxy for \"items scored\". The live value-scoring pass runs through the\n // phase runner above.\n try {\n const ledgerPath = path.join(memoryDir, \"state\", \"observation-ledger\", \"rebuilt-observations.jsonl\");\n let raw = \"\";\n try {\n raw = await readFile(ledgerPath, \"utf-8\");\n } catch {\n // No ledger yet — zero items.\n }\n const lines = raw.split(\"\\n\").filter((l) => l.trim().length > 0);\n const nowMs = Date.now();\n const cutoff = nowMs - 24 * 60 * 60 * 1000;\n itemsProcessed = lines.filter((line) => {\n try {\n const obj = JSON.parse(line) as {\n hour?: string;\n timestamp?: string;\n ts?: string;\n };\n const timestamp = typeof obj.hour === \"string\"\n ? obj.hour\n : typeof obj.ts === \"string\"\n ? obj.ts\n : typeof obj.timestamp === \"string\"\n ? obj.timestamp\n : null;\n if (!timestamp) return false;\n const ms = Date.parse(timestamp);\n return Number.isFinite(ms) && ms >= cutoff && ms < nowMs;\n } catch {\n return false;\n }\n }).length;\n notes = `dry-run: would score ${itemsProcessed} observation entries`;\n } catch (err) {\n throw new Error(`light-sleep scan failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else if (phase === \"rem\") {\n if (!dryRun) {\n if (!phaseRunner) {\n throw new Error(\"REM manual runs require a phase runner\");\n }\n const result = await phaseRunner({ memoryDir, phase });\n itemsProcessed = Math.max(0, Math.floor(result.itemsProcessed));\n notes = result.notes ?? `REM pass assessed ${itemsProcessed} memories`;\n } else {\n // REM dry-runs estimate candidates without synthesizing or archiving.\n try {\n const stateFilePath = path.join(memoryDir, \"state\", \"semantic-consolidation-last-run.json\");\n let lastRunAt: string | null = null;\n try {\n const stateRaw = await readFile(stateFilePath, \"utf-8\");\n const stateData = JSON.parse(stateRaw) as { lastRunAt?: string };\n lastRunAt = stateData.lastRunAt ?? null;\n } catch {\n // No state file — never run.\n }\n const memFiles = await listMemoryFiles(memoryDir);\n itemsProcessed = memFiles.length;\n notes = `dry-run: ${itemsProcessed} memories would enter REM consolidation pass${lastRunAt ? ` (last run: ${lastRunAt})` : \" (never run)\"}`;\n } catch (err) {\n throw new Error(`REM scan failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else {\n // deep sleep\n if (governanceRunner) {\n try {\n const result = await governanceRunner({ memoryDir, dryRun });\n itemsProcessed = result.scannedMemories;\n notes = result.notes ?? (dryRun ? `dry-run: ${result.appliedActionCount} actions proposed` : `${result.appliedActionCount} actions applied`);\n } catch (err) {\n throw new Error(`deep-sleep governance run failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n // Fallback: count memory files.\n const memFiles = await listMemoryFiles(memoryDir);\n itemsProcessed = memFiles.length;\n notes = dryRun\n ? `dry-run: ${itemsProcessed} memories eligible for deep-sleep governance`\n : `assessed ${itemsProcessed} memories for deep-sleep governance`;\n }\n }\n\n const completedAt = new Date().toISOString();\n const durationMs = Math.max(0, Date.parse(completedAt) - Date.parse(startedAt));\n let ledgerEntry: DreamsLedgerEntry | undefined;\n try {\n ledgerEntry = await recordDreamsPhaseRun({\n memoryDir,\n startedAt,\n completedAt,\n phase,\n itemsProcessed,\n dryRun,\n trigger: \"manual\",\n notes,\n });\n } catch {\n // Telemetry is best-effort: the phase work has already completed.\n }\n\n return {\n phase,\n dryRun,\n durationMs: ledgerEntry?.durationMs ?? durationMs,\n itemsProcessed,\n notes,\n ledgerEntry,\n };\n}\n\n// ── Shared governance result mapping ──────────────────────────────────────────\n\nexport function summarizeGovernanceResultForDreams(\n govResult: {\n summary?: { scannedMemories?: number };\n proposedActions: unknown[];\n appliedActions: unknown[];\n reviewQueue: unknown[];\n },\n dryRun: boolean,\n): { scannedMemories: number; appliedActionCount: number; notes?: string } {\n const proposedCount = govResult.proposedActions.length;\n const appliedCount = govResult.appliedActions.length;\n const scannedCount =\n typeof govResult.summary?.scannedMemories === \"number\" &&\n Number.isFinite(govResult.summary.scannedMemories)\n ? Math.max(0, Math.floor(govResult.summary.scannedMemories))\n : govResult.reviewQueue.length;\n return {\n scannedMemories: scannedCount,\n appliedActionCount: appliedCount,\n notes: dryRun\n ? `shadow mode: ${proposedCount} actions proposed`\n : `applied ${appliedCount} actions`,\n };\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\nasync function listMemoryFiles(memoryDir: string): Promise<string[]> {\n const out: string[] = [];\n try {\n const root = await lstat(memoryDir);\n if (root.isSymbolicLink()) {\n throw new Error(`memoryDir must not be a symlink: ${memoryDir}`);\n }\n if (!root.isDirectory()) return out;\n } catch (err) {\n if (err instanceof Error && /must not be a symlink/.test(err.message)) {\n throw err;\n }\n return out;\n }\n\n async function walk(dir: string): Promise<void> {\n let entries: Array<{ name: string; isDirectory(): boolean; isFile(): boolean; isSymbolicLink(): boolean }>;\n try {\n entries = (await readdir(dir, { withFileTypes: true })) as typeof entries;\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.isSymbolicLink()) continue;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n // Skip state/ and archive/ subdirs.\n if (\n entry.name === \"state\" ||\n entry.name === \"archive\" ||\n entry.name === \"namespaces\" ||\n entry.name === \".git\"\n ) continue;\n await walk(full);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n out.push(full);\n }\n }\n }\n\n await walk(memoryDir);\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/namespaces/storage.ts"],"sourcesContent":["import { resolveNamespaceCapabilities, resolveRecallAuxiliaryCapabilities } from \"../capabilities.js\";\nimport path from \"node:path\";\nimport { access, lstat, readdir } from \"node:fs/promises\";\nimport { isSafeRouteNamespace } from \"../routing/engine.js\";\nimport { StorageManager } from \"../storage.js\";\nimport { keyring, secureStoreDir } from \"../secure-store/index.js\";\nimport type { PluginConfig } from \"../types.js\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\nimport {\n namespaceIdentityLegacyToken,\n namespaceIdentityToken,\n normalizeNamespaceIdentity,\n} from \"./identity.js\";\nimport type { NamespaceCatalog } from \"./catalog.js\";\nimport { MutationSerializer } from \"../utils/serialize-mutations.js\";\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function hasStoredEntries(p: string): Promise<boolean> {\n try {\n const entry = await lstat(p);\n if (entry.isSymbolicLink()) return true;\n if (!entry.isDirectory()) return true;\n const children = await readdir(p, { withFileTypes: true });\n for (const child of children) {\n const childPath = path.join(p, child.name);\n if (child.isSymbolicLink() || child.isFile()) return true;\n if (child.isDirectory() && (await hasStoredEntries(childPath))) return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n// Build a per-namespace directory under `<memoryDir>/namespaces` and assert the\n// resolved path stays inside that base. Namespace identifiers can originate from\n// operator config (config.defaultNamespace) and request-derived routing, so this\n// containment check prevents directory traversal (CodeQL js/path-injection).\n// For safe segments this returns exactly `path.join(base, segment)`, so there is\n// no behavioral change for valid namespaces.\nfunction resolveNamespaceDir(memoryDir: string, segment: string): string {\n // Mirror isSafeRouteNamespace's separator/parent-ref rejection (without its\n // 64-char cap, so identity tokens still pass). Rejecting separators and \"..\"\n // up front keeps the value a single contained child of <memoryDir>/namespaces.\n if (\n segment.length === 0 ||\n segment.includes(\"/\") ||\n segment.includes(\"\\\\\") ||\n segment.includes(\"..\") ||\n path.isAbsolute(segment)\n ) {\n throw new Error(`unsafe namespace path segment: ${segment}`);\n }\n return path.join(memoryDir, \"namespaces\", segment);\n}\n\nconst LEGACY_NAMESPACE_CONTENT_CHILDREN = [\n ...ALL_CATEGORY_DIRS,\n \"entities\",\n \"artifacts\",\n \"identity\",\n \"config\",\n \"summaries\",\n \"profile.md\",\n] as const;\n\nconst LEGACY_NAMESPACE_RUNTIME_CHILDREN = [\"state\"] as const;\n\nasync function hasAnyLegacyData(\n rootDir: string,\n options: { includeRuntimeState?: boolean } = {},\n): Promise<boolean> {\n const children = options.includeRuntimeState === true\n ? [...LEGACY_NAMESPACE_CONTENT_CHILDREN, ...LEGACY_NAMESPACE_RUNTIME_CHILDREN]\n : LEGACY_NAMESPACE_CONTENT_CHILDREN;\n for (const child of children) {\n if (await hasStoredEntries(path.join(rootDir, child))) return true;\n }\n return false;\n}\n\nasync function hasAnyNamespaceStorageMarker(\n rootDir: string,\n options: { includeRuntimeState?: boolean } = {},\n): Promise<boolean> {\n const children = options.includeRuntimeState === true\n ? [...LEGACY_NAMESPACE_CONTENT_CHILDREN, ...LEGACY_NAMESPACE_RUNTIME_CHILDREN]\n : LEGACY_NAMESPACE_CONTENT_CHILDREN;\n for (const child of children) {\n if (await exists(path.join(rootDir, child))) return true;\n }\n return false;\n}\n\n/**\n * Storage routing for namespaces.\n *\n * Compatibility note:\n * - When namespaces are enabled, existing raw namespace roots are preserved.\n * New namespace roots use tokenized names under `memoryDir/namespaces/<token>`.\n * - The default namespace continues to use the legacy `memoryDir` root unless the caller\n * has created `memoryDir/namespaces/<defaultNamespace>` (in which case we use that).\n *\n * This avoids surprising \"lost memories\" when an install flips namespaces on without\n * migrating existing data.\n */\n/**\n * Optional hooks for the storage router. `onResolve` fires whenever a namespace's\n * storage is resolved/created, so a downstream consumer (e.g. the namespace\n * catalog, issue #1499) can register the namespace. The hook MUST NOT throw into\n * the router; the router invokes it defensively and a hook failure never affects\n * storage resolution.\n *\n * The hook MAY return (or resolve to) a boolean indicating whether the\n * registration actually PERSISTED (round 6, codex P2 — NEFoX). When it resolves\n * to `false` (a dropped/no-op registration), the router does NOT mark the\n * (namespace, storageDir) pair as notified, so the next resolve RETRIES it\n * instead of suppressing it forever. A `void`/`undefined` result is treated as\n * success (legacy hooks).\n */\nexport interface NamespaceStorageRouterHooks {\n onResolve?: (\n namespace: string,\n storageDir: string,\n ) => void | boolean | Promise<void | boolean>;\n}\n\n/**\n * Resolve the runtime storage root for the configured DEFAULT namespace.\n *\n * Shared between the live router (`NamespaceStorageRouter.defaultNamespaceRoot`)\n * and the rebuildable catalog (`NamespaceCatalog.rebuildFromDisk`) so the two\n * can never diverge (CLAUDE.md rule #22/#42 — read & write paths resolve through\n * the same logic). The contract is: while legacy memory data still lives\n * directly under `memoryDir`, the default root stays `memoryDir`; only once the\n * legacy root is empty and a `namespaces/<default|token>` dir holds data does\n * the default migrate into that tokenized/legacy-named dir.\n */\nexport async function resolveDefaultNamespaceRoot(config: PluginConfig): Promise<string> {\n if (!resolveNamespaceCapabilities(config).namespaces) {\n return config.memoryDir;\n }\n\n // Build the legacy default root from the NORMALIZED (trimmed) name so a\n // whitespace-padded `defaultNamespace` still finds the live `namespaces/default`\n // root (NIabe). `storageFor()` classifies the trimmed value as the default, and\n // the on-disk legacy dir is created under the trimmed name; using the raw spaced\n // name here would look for `namespaces/<spaced>` and miss the real root, falling\n // back to memoryDir/tokenized. `namespaceIdentityToken` already normalizes\n // internally, so the tokenized path is unaffected.\n const defaultIdentity = normalizeNamespaceIdentity(config.defaultNamespace);\n const defaultNfdIdentity = defaultIdentity.normalize(\"NFD\");\n const legacyNsDir = resolveNamespaceDir(config.memoryDir, defaultIdentity);\n const legacyNfdNsDir = resolveNamespaceDir(config.memoryDir, defaultNfdIdentity);\n const tokenizedNsDir = resolveNamespaceDir(\n config.memoryDir,\n namespaceIdentityToken(defaultIdentity),\n );\n const legacyTokenNsDir = resolveNamespaceDir(\n config.memoryDir,\n namespaceIdentityLegacyToken(defaultNfdIdentity),\n );\n const tokenizedHasData =\n (await exists(tokenizedNsDir)) &&\n (await hasAnyNamespaceStorageMarker(tokenizedNsDir, { includeRuntimeState: true }));\n const legacyTokenHasData =\n (await exists(legacyTokenNsDir)) &&\n (await hasAnyNamespaceStorageMarker(legacyTokenNsDir, { includeRuntimeState: true }));\n const legacyHasData =\n (await exists(legacyNsDir)) &&\n (await hasAnyNamespaceStorageMarker(legacyNsDir, { includeRuntimeState: true }));\n const legacyNfdHasData =\n legacyNfdNsDir !== legacyNsDir &&\n (await exists(legacyNfdNsDir)) &&\n (await hasAnyNamespaceStorageMarker(legacyNfdNsDir, { includeRuntimeState: true }));\n const nsDir = tokenizedHasData\n ? tokenizedNsDir\n : legacyTokenHasData\n ? legacyTokenNsDir\n : legacyHasData\n ? legacyNsDir\n : legacyNfdHasData\n ? legacyNfdNsDir\n : (await exists(legacyNsDir))\n ? legacyNsDir\n : legacyNfdNsDir !== legacyNsDir && (await exists(legacyNfdNsDir))\n ? legacyNfdNsDir\n : tokenizedNsDir;\n return (await exists(nsDir)) && !(await hasAnyLegacyData(config.memoryDir))\n ? nsDir\n : config.memoryDir;\n}\n\n/**\n * Resolve the runtime storage root for ANY namespace exactly as the live router\n * would (`NamespaceStorageRouter.namespaceRoot`). Shared so the rebuildable\n * catalog records the SAME on-disk root the router routes to — a recall/read\n * touch must not guess `namespaces/<token>` when the router actually serves a\n * legacy raw-name dir or a migrated default root (CLAUDE.md rule #22/#42; round\n * 4, cursor Medium). The default namespace delegates to `resolveDefaultNamespaceRoot`;\n * every other namespace prefers the tokenized root when it has a storage marker,\n * else a legacy raw-name dir when present, else the tokenized root.\n */\nexport async function resolveNamespaceStorageRoot(\n config: PluginConfig,\n namespace: string,\n): Promise<string> {\n if (!resolveNamespaceCapabilities(config).namespaces) return config.memoryDir;\n // Compare on NORMALIZED identity so a whitespace-padded configured default name\n // still routes to the default root rather than a tokenized non-default dir\n // (NH-FH). The catalog keys records by the same normalized identity.\n if (normalizeNamespaceIdentity(namespace) === normalizeNamespaceIdentity(config.defaultNamespace)) {\n return resolveDefaultNamespaceRoot(config);\n }\n const normalized = normalizeNamespaceIdentity(namespace);\n const legacyExactNamespace = namespace.trim();\n const legacyNfdNamespace = normalized.normalize(\"NFD\");\n const legacyRoot = resolveNamespaceDir(config.memoryDir, normalized);\n const legacyExactRoot =\n legacyExactNamespace !== normalized && isSafeRouteNamespace(legacyExactNamespace)\n ? resolveNamespaceDir(config.memoryDir, legacyExactNamespace)\n : null;\n const legacyNfdRoot =\n legacyNfdNamespace !== normalized\n ? resolveNamespaceDir(config.memoryDir, legacyNfdNamespace)\n : null;\n const tokenizedRoot = resolveNamespaceDir(config.memoryDir, namespaceIdentityToken(normalized));\n const legacyTokenRoots = [\n namespaceIdentityLegacyToken(legacyExactNamespace),\n namespaceIdentityLegacyToken(legacyNfdNamespace),\n ]\n .filter((token, index, tokens) => tokens.indexOf(token) === index)\n .map((token) => resolveNamespaceDir(config.memoryDir, token));\n for (const candidate of [tokenizedRoot, ...legacyTokenRoots]) {\n if (\n (await exists(candidate)) &&\n (await hasAnyNamespaceStorageMarker(candidate, { includeRuntimeState: true }))\n ) {\n return candidate;\n }\n }\n for (const candidate of [legacyRoot, legacyExactRoot, legacyNfdRoot]) {\n if (candidate && (await exists(candidate))) return candidate;\n }\n return tokenizedRoot;\n}\n\nexport class NamespaceStorageRouter {\n private readonly cache = new Map<string, StorageManager>();\n private defaultNsRootResolved: string | null = null;\n // Dedup the resolve hook (round 6, cursor Medium — NCNL2). Recall/extraction\n // call `storageFor` repeatedly; firing `onResolve` (→ catalog loadCompacted +\n // append) on every cache hit grows `namespaces.jsonl` without bound between\n // rebuilds. We fire the hook only when the (namespace, storageDir) pair is new\n // or its dir changed, so a steady-state cache hit is a no-op for the catalog.\n private readonly notifiedResolved = new Map<string, string>();\n // Instance-scoped serializer for the resolve hook (issue #1524 adoption).\n // Replaces the bespoke `inFlightResolved` marker-then-clear pattern: the\n // serializer's per-key chain strictly orders concurrent notifications for the\n // SAME namespace, and the task re-checks `notifiedResolved` once it runs, so a\n // burst of cache hits before the first hook settles collapses to a single\n // hook invocation (the once-per-namespace intent). Recovery is preserved —\n // one rejected hook never poisons subsequent notifications for that key.\n private readonly resolveSerializer = new MutationSerializer();\n // (namespace, storageDir) pairs whose resolve hook is currently pending. Set SYNCHRONOUSLY in\n // notifyResolved (before queueing) so a burst of concurrent storageFor()\n // cache hits collapses onto the one in-flight hook instead of each enqueuing\n // its own task. Cleared when the queued hook settles. Without this, a dropped\n // hook (returns false on a rebuild-lock timeout) leaves notifiedResolved\n // unset, so every queued sibling task would re-run the hook — N serial lock\n // waits (cursor Medium 06f58a7c, codex P2).\n private readonly inFlightResolveHooks = new Set<string>();\n // Tracks every in-flight resolve-hook promise so callers can deterministically\n // await the fire-and-forget registrations that `storageFor()` kicks off (see\n // `whenResolveHooksSettled`). Entries are removed as each hook settles, so the\n // set holds at most one promise per concurrently-resolving namespace.\n private readonly pendingResolveHooks = new Set<Promise<unknown>>();\n // Pending post-write catalog touch promises (#1522). Like pendingResolveHooks,\n // lets tests await fire-and-forget write touches deterministically.\n private readonly pendingWriteTouches = new Set<Promise<unknown>>();\n\n // Normalized (trimmed) default namespace identity (NH-FH). `storageFor`\n // normalizes its input, so default-namespace branches must compare against the\n // normalized config default too — otherwise a whitespace-padded configured\n // default name routes the default namespace to a tokenized non-default root.\n private readonly defaultNamespaceIdentity: string;\n\n /** #2019: hook fired when a new namespace storage is created via storageFor. */\n onStorageCreated?: (storage: StorageManager, namespace: string) => void;\n\n constructor(\n private readonly config: PluginConfig,\n private readonly hooks: NamespaceStorageRouterHooks = {},\n /** Catalog reference for post-write/read touches (issue #1522 chokepoint). */\n private readonly catalog?: NamespaceCatalog,\n ) {\n this.defaultNamespaceIdentity = normalizeNamespaceIdentity(config.defaultNamespace);\n }\n\n private async defaultNamespaceRoot(): Promise<string> {\n this.defaultNsRootResolved = await resolveDefaultNamespaceRoot(this.config);\n return this.defaultNsRootResolved;\n }\n\n private async namespaceRoot(namespace: string): Promise<string> {\n // NOTE: only used after defaultNamespaceRoot() resolution.\n if (!resolveNamespaceCapabilities(this.config).namespaces) return this.config.memoryDir;\n if (normalizeNamespaceIdentity(namespace) === this.defaultNamespaceIdentity) {\n return this.defaultNsRootResolved ?? this.config.memoryDir;\n }\n return resolveNamespaceStorageRoot(this.config, namespace);\n }\n\n async storageFor(namespace: string): Promise<StorageManager> {\n const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);\n if (ns !== this.defaultNamespaceIdentity && !isSafeRouteNamespace(ns)) {\n throw new Error(`unsafe namespace: ${ns}`);\n }\n // Even when the default namespace is exempt from the check above, every\n // on-disk path is built through resolveNamespaceDir(), which rejects\n // traversal segments — so an unsafe configured default still cannot escape\n // <memoryDir>/namespaces (CodeQL js/path-injection).\n\n let root: string;\n if (ns === this.defaultNamespaceIdentity) {\n root = await this.defaultNamespaceRoot();\n const cached = this.cache.get(ns);\n if (cached && cached.dir === root) {\n await this.applySecureStoreConfig(cached);\n this.notifyResolved(ns, root);\n return cached;\n }\n } else {\n const cached = this.cache.get(ns);\n root = await this.namespaceRoot(ns);\n if (cached && cached.dir === root) {\n await this.applySecureStoreConfig(cached);\n this.notifyResolved(ns, root);\n return cached;\n }\n }\n\n // Pass the hot-memories cache gate explicitly (issue #1902, Codex P2): a\n // per-namespace child root is never registered in the per-dir default map,\n // so without this it would fall back to the process-wide default and ignore\n // THIS daemon's hotMemoriesCacheEnabled config. Threading it from config\n // keeps every namespace storage aligned with its owning orchestrator.\n const sm = new StorageManager(\n root,\n this.config.entitySchemas,\n this.config.hotMemoriesCacheEnabled,\n );\n // Register this child root's gate AND TTL in the per-dir maps (issue #1902,\n // Codex P2). The constructor override above fixes the child's own gate, but\n // its TTL is resolved via the per-dir map (hotCacheTtlMs); an unregistered\n // child root would fall back to the last process-wide TTL and could inherit\n // another daemon's value. Registering keyed by root keeps both per-dir.\n StorageManager.setHotMemoriesCacheDefault(\n root,\n this.config.hotMemoriesCacheEnabled,\n this.config.hotMemoriesCacheTtlMs,\n );\n const { scopedCacheInvalidationEnabled } = this.config;\n StorageManager.setScopedCacheInvalidationDefault(root, scopedCacheInvalidationEnabled);\n // Propagate the inline-attribution template so that router-created storages\n // (used by extraction and shared-promotion paths) strip citations consistently,\n // matching the behaviour of the primary this.storage instance in the orchestrator.\n sm.citationTemplate = this.config.inlineSourceAttributionFormat;\n // #1522: install the post-write catalog touch at the chokepoint — every\n // successful write on this StorageManager records the namespace touch.\n this.bindCatalogWriteHook(sm, ns);\n // #1579: apply the tombstone non-resurrection config so every namespace\n // storage enforces the invariant at its own writeMemory chokepoint.\n this.applyTombstonesConfig(sm, ns);\n // #2033: install the secure-store context on router-created stores. The\n // orchestrator keys ONLY its primary default-namespace storage; without\n // this, a store resolved through the router reads isSecureStoreUnlocked()\n // === false even after the operator unlocked the keyring, so secure-store\n // recovery paths (e.g. the CLI lifecycle-ledger rebuild) refuse an encrypted\n // ledger they could actually rewrite, and namespace writes could silently\n // fall back to plaintext. Mirror the orchestrator's setup exactly.\n await this.applySecureStoreConfig(sm);\n this.cache.set(ns, sm);\n // #2019: let the orchestrator wire write-triggered prioritized embedding on\n // every router-created namespace storage, not just the primary default store.\n this.onStorageCreated?.(sm, ns);\n this.notifyResolved(ns, root);\n return sm;\n }\n\n /**\n * Fire the resolve hook defensively. A hook failure (e.g. a catalog write\n * error) MUST NOT crash storage resolution — see CLAUDE.md gotcha #13.\n *\n * Issue #1524 adoption: hook invocations for the SAME namespace are now\n * strictly ordered through the shared `MutationSerializer` rather than the\n * bespoke `inFlightResolved` marker-then-clear pattern. The serializer\n * guarantees one in-flight hook per namespace; a concurrent burst of\n * `storageFor()` cache hits collapses to a single hook invocation because\n * each queued task re-checks `notifiedResolved` before invoking the hook. A\n * dropped (`false`) or rejected hook leaves `notifiedResolved` unset so the\n * next `storageFor()` retries — the serializer recovers from the rejection,\n * so a failed hook never poisons later notifications.\n */\n private notifyResolved(namespace: string, storageDir: string): void {\n const hook = this.hooks.onResolve;\n if (!hook) return;\n // Permanent dedup: skip once we've SUCCESSFULLY notified this exact\n // (namespace, storageDir). A changed dir (rare: migration/realignment)\n // still re-fires once. The mark is set ONLY AFTER the hook succeeds, so a\n // dropped registration (e.g. rebuild-lock timeout) is RETRIED on the next\n // cache hit instead of being suppressed forever (round 6, cursor Medium —\n // ND3EJ).\n if (this.notifiedResolved.get(namespace) === storageDir) return;\n // In-flight dedup (cursor Medium 06f58a7c, codex P2): if a hook for THIS\n // namespace is already pending, collapse this call onto it instead of\n // enqueueing another task. The serializer strictly orders queued tasks, but\n // ordering alone is not enough — when the first hook returns `false`\n // (dropped touch, e.g. rebuild-lock timeout), `notifiedResolved` stays\n // unset, so each queued sibling task would pass its re-check and re-run the\n // hook. With the real catalog hook each retry can spend the full lock wait,\n // so N cache hits during a rebuild leave N serial background lock attempts.\n // Collapsing here means at most ONE hook runs per in-flight registration;\n // its result (set `notifiedResolved` on success, leave unset on drop)\n // decides whether a LATER `storageFor()` retries — collapsing loses\n // nothing because the drop is already retried on the next cache hit.\n // Keyed by the composite (namespace, storageDir) so a CHANGED dir\n // (migration/realignment) for the same namespace still gets its own hook —\n // it is NOT collapsed onto the old dir's pending registration (cursor\n // Medium, codex P2).\n const inFlightKey = namespace + \"\\u0000\" + storageDir;\n if (this.inFlightResolveHooks.has(inFlightKey)) return;\n this.inFlightResolveHooks.add(inFlightKey);\n // Queue through the serializer. Concurrent calls for the same namespace\n // strictly order here; the 2nd call's task runs only after the 1st's hook\n // settles, by which point `notifiedResolved` is either set (no-op) or still\n // unset (retry). The returned promise is tracked for\n // `whenResolveHooksSettled()`. Rejections from the task body are caught so\n // they never reach the caller (best-effort hook contract); the serializer's\n // recovered tail still lets subsequent notifications run.\n const task = async (): Promise<void> => {\n // Re-check after queueing: a prior task in this same chain may have just\n // marked the pair as notified.\n if (this.notifiedResolved.get(namespace) === storageDir) return;\n try {\n // Hook may be sync or async; Promise.resolve normalizes both. A result\n // of `false` means the registration was dropped/no-op (e.g. rebuild-lock\n // timeout) — leave notifiedResolved UNSET so the next storageFor retries\n // (round 6, codex P2 — NEFoX). `void`/`undefined` is success for legacy\n // hooks. A rejection leaves notifiedResolved unset for the same reason.\n const persisted = await Promise.resolve(hook(namespace, storageDir));\n if (persisted !== false) {\n this.notifiedResolved.set(namespace, storageDir);\n }\n } catch {\n // Best-effort: a hook failure MUST NOT crash storage resolution. Leave\n // notifiedResolved unset so a later storageFor retries the registration.\n }\n };\n const queued = this.resolveSerializer.serialize(namespace, task);\n this.pendingResolveHooks.add(queued);\n // Clear the in-flight marker when the hook settles so a subsequent\n // storageFor() can retry after a drop, or short-circuit via notifiedResolved\n // after a success.\n const cleanup = (): void => {\n this.inFlightResolveHooks.delete(inFlightKey);\n this.pendingResolveHooks.delete(queued);\n };\n void queued.then(cleanup, cleanup);\n }\n\n /**\n * Install the post-write catalog touch hook on an externally-constructed\n * StorageManager (issue #1522). Used by the orchestrator for the legacy\n * default-namespace storage (`this.storage`) that bypasses the router.\n */\n bindCatalogWriteHook(sm: StorageManager, namespace: string): void {\n // Issue #1903: pure state-file writes only touch the catalog when explicitly\n // opted in. Router-created storages (storageFor) and the legacy default\n // storage both flow through here, so this is the single wiring point.\n sm.touchStateWrites = this.config.namespacesCatalogTouchStateWrites === true;\n sm.onCatalogWrite = () => this.touchCatalogWrite(namespace, sm.dir);\n }\n\n /**\n * Install the tombstone config on an externally-constructed StorageManager\n * (issue #1579). Mirrors `bindCatalogWriteHook` — used for the legacy\n * default-namespace storage that bypasses the router. Router-created\n * storages are wired inline in `storageFor()` via `applyTombstonesConfig`.\n */\n bindTombstonesConfig(\n sm: StorageManager,\n namespace: string,\n config: { enabled: boolean; semanticMatch: boolean; semanticThreshold: number },\n ): void {\n this.tombstonesGlobalConfig = { ...config };\n sm.setTombstonesConfig({ ...config, namespace });\n }\n\n private tombstonesGlobalConfig: {\n enabled: boolean;\n semanticMatch: boolean;\n semanticThreshold: number;\n } = { enabled: false, semanticMatch: false, semanticThreshold: 0.9 };\n\n /**\n * Apply the tombstone config to a router-created StorageManager. Called\n * inline in `storageFor()` so every namespace storage enforces the\n * non-resurrection invariant, namespace-scoped (rule 42).\n */\n private applyTombstonesConfig(sm: StorageManager, namespace: string): void {\n sm.setTombstonesConfig({ ...this.tombstonesGlobalConfig, namespace });\n }\n\n /**\n * Install the at-rest secure-store context on a router-created StorageManager\n * (#2033). Mirrors the orchestrator's primary-storage setup: mark the store\n * required so a locked store throws instead of silently writing plaintext, and\n * install the master key when the process-local keyring already holds it (the\n * operator unlocked). The secure store is one key per memory ROOT (single\n * header + per-file path-bound AAD), so every namespace store keys off the\n * root keyring id — exactly the id the orchestrator and `secure-store unlock`\n * register under. No-op when secure-store is disabled.\n */\n private async applySecureStoreConfig(sm: StorageManager): Promise<void> {\n if (!resolveRecallAuxiliaryCapabilities(this.config).secureStore) return;\n sm.setSecureStoreRequired(true);\n const existingKey = keyring.getKey(secureStoreDir(this.config.memoryDir));\n if (existingKey) {\n await sm.setSecureStoreKeyAndWait(existingKey, this.config.secureStoreEncryptOnWrite);\n }\n }\n\n /**\n * Post-write catalog touch (issue #1522 chokepoint). Called by every\n * StorageManager's post-write hook AFTER a successful write. Best-effort\n * and failure-tolerant — a catalog error MUST NOT affect the primary write\n * (gotcha #13, rule #40). Fire-and-forget by design.\n */\n private touchCatalogWrite(namespace: string, storageDir: string): void {\n if (!this.catalog) return;\n const touch = this.catalog\n .markWrite(namespace, { discoveredBy: \"write\", storageDir })\n .catch(() => undefined);\n this.pendingWriteTouches.add(touch);\n void touch.then(\n () => { this.pendingWriteTouches.delete(touch); },\n () => { this.pendingWriteTouches.delete(touch); },\n );\n }\n\n /**\n * Record a namespace read in the catalog (issue #1522 chokepoint move).\n * Best-effort and failure-tolerant. Used by recall paths so the read touch\n * lives in the storage layer, not at the caller.\n */\n recordRead(namespace: string, storageDir?: string): void {\n if (!this.catalog) return;\n const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);\n void this.catalog\n .markRead(ns, { discoveredBy: \"read\", storageDir })\n .catch(() => undefined);\n }\n /**\n * Record a namespace write touch in the catalog (issue #1522). Best-effort\n * and failure-tolerant. Used by consolidation/cleanup passes that may mutate\n * the store via delete-only paths (e.g. entity-file merges, TTL cleanup) so\n * the namespace's lastWriteAt stays fresh even when no explicit write went\n * through the storage chokepoint. This is NOT a substitute for the chokepoint\n * — it's a belt-and-suspenders for paths the chokepoint doesn't cover.\n */\n recordWrite(namespace: string, storageDir?: string): void {\n if (!this.catalog) return;\n const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);\n const touch = this.catalog\n .markWrite(ns, { discoveredBy: \"write\", storageDir })\n .catch(() => undefined);\n this.pendingWriteTouches.add(touch);\n void touch.then(\n () => { this.pendingWriteTouches.delete(touch); },\n () => { this.pendingWriteTouches.delete(touch); },\n );\n }\n\n /**\n * Resolve once every in-flight post-write catalog touch has settled (#1522).\n * Mirrors `whenResolveHooksSettled()`: the StorageManager's post-write hook\n * fires the catalog touch fire-and-forget, so tests asserting lastWriteAt\n * moved should await this instead of racing a timer.\n */\n async whenWriteTouchesSettled(): Promise<void> {\n // Flush any coalesced (buffered) touches first so their appends join the\n // pending set below — otherwise this could resolve while a buffered touch's\n // lastWriteAt is still unwritten on disk (#1903, Cursor). No-op when\n // coalescing is disabled (window 0) or the catalog is inert.\n await this.catalog?.flushPendingTouches().catch(() => undefined);\n while (this.pendingWriteTouches.size > 0) {\n await Promise.allSettled([...this.pendingWriteTouches]);\n }\n }\n\n /**\n * Resolve once every in-flight `onResolve` registration has settled.\n *\n * `storageFor()` fires the resolve hook fire-and-forget, so its catalog side\n * effect (e.g. `registerResolved(...)`) is not observable the moment\n * `storageFor()` returns. Callers that must act on that side effect — notably\n * tests asserting the catalog was updated — should await this instead of\n * racing a timer. Resolves immediately when no hook is registered or nothing\n * is in flight. The loop re-checks because a settling hook could, in\n * principle, trigger a follow-on resolution.\n */\n async whenResolveHooksSettled(): Promise<void> {\n while (this.pendingResolveHooks.size > 0) {\n await Promise.allSettled([...this.pendingResolveHooks]);\n }\n }\n\n /**\n * #2019: iterate the currently cached router storages with their namespace so\n * callers can wire hooks on stores resolved before the hook was installed.\n */\n forEachCachedStorage(fn: (storage: StorageManager, namespace: string) => void): void {\n for (const [namespace, storage] of this.cache) fn(storage, namespace);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/consolidation-provenance-check.ts"],"sourcesContent":["/**\n * Consolidation provenance integrity check (issue #561 PR 4).\n *\n * Validates that every memory carrying consolidation provenance frontmatter\n * (`derived_from`, `derived_via`) resolves to real data:\n *\n * - Each `derived_from` entry `\"<path>:<version>\"` must name a\n * page-version snapshot that exists on disk (via the sidecar layout\n * documented in `page-versioning.ts`).\n * - Each `derived_via` must be one of the known\n * `ConsolidationOperator` values — malformed values are surfaced as\n * warnings rather than crashes so legacy or future operators survive a\n * rollback.\n *\n * Non-fatal: every failure renders a warning with the offending file path\n * and a human-readable reason. Integrity problems are informational for\n * now — we do not auto-heal or archive broken memories.\n */\n\nimport path from \"node:path\";\nimport { lstat, readdir, readFile, realpath, stat } from \"node:fs/promises\";\nimport { constants as fsConstants } from \"node:fs\";\nimport type { StorageManager } from \"./storage.js\";\nimport {\n DERIVED_FROM_MEMORY_ID_RE,\n isConsolidationOperator,\n} from \"./consolidation-operator.js\";\n// Import the canonical `sidecarKey` from page-versioning (PR #634\n// review, cursor Medium) so a future key-format change stays in\n// lock-step with the doctor scan.\nimport { sidecarKey } from \"./page-versioning.js\";\nimport { RECALL_FALLBACK_DIRS } from \"./utils/category-dir.js\";\n\n/**\n * Regex to spot a `derived_via: <value>` line in the raw YAML frontmatter\n * between the opening and first closing `---` delimiters. We use the raw\n * text rather than the parsed `frontmatter.derived_via` because the\n * read-path parser coerces unknown values back to `undefined` — that\n * would silently hide corrupted-or-future operators from the doctor scan\n * (PR #634 review feedback, codex P2).\n */\n// Allow empty capture groups so truncated/blank `derived_via:` and\n// `derived_from:` lines (key present, no value) are distinguishable\n// from \"key missing entirely\" (regex returns null). Optional\n// leading whitespace accepts indented keys which `parseFrontmatter`\n// also accepts (PR #634 round-6 review, codex P2).\nconst DERIVED_VIA_RAW_RE = /^[\\t ]*derived_via:[\\t ]*(.*)$/mu;\nconst DERIVED_FROM_RAW_RE = /^[\\t ]*derived_from:[\\t ]*(.*)$/mu;\n\n/**\n * Tokenize a YAML-block-style list under `key:` in the given\n * frontmatter slice. Looks for lines matching `^ - <value>` after a\n * `key:` line and before the next non-list line. Returns `null` when\n * the key is missing or the value is a scalar / flow list (no block\n * entries found).\n *\n * Only used for the mixed-list malformed-entry detection — it does\n * not try to decode YAML escape sequences since we only need the\n * entry count + raw token text to compare against the parsed array.\n */\nfunction tokenizeRawBlockList(fmSlice: string, key: string): string[] | null {\n const lines = fmSlice.split(\"\\n\");\n // Accept indented keys too — parseFrontmatter does (PR #634 round-7\n // review, codex P2 / cursor Low).\n const keyRe = new RegExp(`^[\\\\t ]*${key}:[\\\\t ]*(.*)$`, \"u\");\n let startIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(keyRe);\n if (m) {\n if (m[1].trim().length === 0) {\n startIdx = i + 1;\n }\n break;\n }\n }\n if (startIdx < 0) return null;\n const items: string[] = [];\n for (let i = startIdx; i < lines.length; i++) {\n const line = lines[i];\n if (!/^\\s+-/.test(line)) break; // not a block-list entry\n const m = line.match(/^\\s+-\\s*(.*)$/u);\n if (!m) break;\n let tok = m[1].trim();\n if (\n (tok.startsWith('\"') && tok.endsWith('\"') && tok.length >= 2) ||\n (tok.startsWith(\"'\") && tok.endsWith(\"'\") && tok.length >= 2)\n ) {\n tok = tok.slice(1, -1);\n }\n items.push(tok);\n }\n return items.length > 0 ? items : null;\n}\n\n/**\n * Tokenize a YAML-flow-style list (`[\"a\", \"b\", ...]`) into a flat\n * string array. Returns `null` when the input isn't a flow list.\n * Best-effort — we don't implement a full YAML parser, just enough to\n * detect mixed valid/invalid entries for the doctor integrity check.\n */\nfunction tokenizeRawFlowList(raw: string): string[] | null {\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"[\") || !trimmed.endsWith(\"]\")) return null;\n const inner = trimmed.slice(1, -1);\n const parts: string[] = [];\n let current = \"\";\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (inDouble) {\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n current += inner[++i];\n continue;\n }\n if (ch === '\"') {\n inDouble = false;\n continue;\n }\n current += ch;\n } else if (inSingle) {\n if (ch === \"'\" && inner[i + 1] === \"'\") {\n current += \"'\";\n i++;\n continue;\n }\n if (ch === \"'\") {\n inSingle = false;\n continue;\n }\n current += ch;\n } else if (ch === '\"') {\n inDouble = true;\n } else if (ch === \"'\") {\n inSingle = true;\n } else if (ch === \",\") {\n parts.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n if (current.trim().length > 0 || parts.length > 0) {\n parts.push(current.trim());\n }\n return parts;\n}\n\n/**\n * One integrity warning attached to a specific memory.\n */\nexport interface ConsolidationProvenanceIssue {\n /** Absolute path to the memory markdown file. */\n memoryPath: string;\n /** Memory id from frontmatter. */\n memoryId: string;\n /** Type of integrity issue. */\n kind:\n | \"derived_from_missing_snapshot\"\n | \"derived_from_malformed_entry\"\n | \"derived_via_unknown_operator\";\n /** Human-readable detail — includes the offending value when relevant. */\n detail: string;\n}\n\n/**\n * Summary of a provenance-integrity scan. Used by the operator-doctor\n * report and surfaced in the CLI output.\n */\nexport interface ConsolidationProvenanceReport {\n /** Total memories inspected. */\n scanned: number;\n /** Memories that carry `derived_from` and/or `derived_via`. */\n withProvenance: number;\n /** One entry per problem detected (may be empty). */\n issues: ConsolidationProvenanceIssue[];\n}\n\nconst DERIVED_FROM_ENTRY_RE = /^(.+):(\\d+)$/;\n\n/**\n * Build the on-disk snapshot path for a `\"<relpath>:<version>\"` entry,\n * relative to the given memory directory. Mirrors the layout documented\n * in `page-versioning.ts`:\n *\n * memoryDir/<sidecarDir>/<sidecarKey>/<version><ext>\n */\nfunction resolveSnapshotPath(\n memoryDir: string,\n sidecarDir: string,\n entry: string,\n): { ok: true; snapshotPath: string } | { ok: false; reason: string } {\n const match = entry.match(DERIVED_FROM_ENTRY_RE);\n if (!match) {\n return { ok: false, reason: `malformed entry (expected \"<path>:<version>\")` };\n }\n const pagePath = match[1];\n const versionId = match[2];\n const ext = path.extname(pagePath) || \".md\";\n const key = sidecarKey(pagePath);\n const snapshotPath = path.join(memoryDir, sidecarDir, key, `${versionId}${ext}`);\n return { ok: true, snapshotPath };\n}\n\n/**\n * Scan every memory under `storage` and flag consolidation-provenance\n * problems. Does not throw on individual failures — collects them in the\n * returned report.\n */\nexport async function runConsolidationProvenanceCheck(options: {\n storage: StorageManager;\n memoryDir: string;\n /**\n * Page-versioning sidecar directory name. Defaults to `.versions` —\n * matches the baked-in default used by `setVersioningConfig` when\n * versioning is enabled via config.\n */\n sidecarDir?: string;\n}): Promise<ConsolidationProvenanceReport> {\n const { storage, memoryDir } = options;\n const sidecarDir = options.sidecarDir ?? \".versions\";\n\n const report: ConsolidationProvenanceReport = {\n scanned: 0,\n withProvenance: 0,\n issues: [],\n };\n\n let memories;\n try {\n memories = await storage.readAllMemories();\n } catch {\n // If we can't enumerate memories at all, surface a single synthetic\n // issue rather than throwing — the doctor wrapper treats an empty\n // issues list as \"ok\" and we don't want a filesystem hiccup to crash\n // the whole diagnostic.\n return {\n scanned: 0,\n withProvenance: 0,\n issues: [\n {\n memoryPath: memoryDir,\n memoryId: \"(unreadable)\",\n kind: \"derived_from_malformed_entry\",\n detail: \"Could not enumerate memory directory to scan provenance.\",\n },\n ],\n };\n }\n\n for (const memory of memories) {\n report.scanned += 1;\n const fm = memory.frontmatter;\n const derivedFrom = fm.derived_from;\n const derivedVia = fm.derived_via;\n\n // Raw frontmatter values from disk — the read-path parser coerces\n // malformed `derived_from` and unknown `derived_via` back to\n // `undefined`, which would silently hide on-disk corruption from\n // the doctor scan (PR #634 review feedback, codex P2). We\n // re-extract both via regex so integrity issues are reported even\n // when the parser normalized them away. `rawDerivedVia` /\n // `rawDerivedFrom` being `\"\"` (empty string) represents a\n // corrupted file with the key present but the value truncated —\n // that's distinct from \"key missing entirely\" (undefined).\n let rawDerivedVia: string | undefined;\n let rawDerivedFrom: string | undefined;\n let rawDerivedViaKeyPresent = false;\n let rawDerivedFromKeyPresent = false;\n let duplicateViaKeys = false;\n let duplicateFromKeys = false;\n let viaMatchCount = 0;\n let fromMatchCount = 0;\n let fmSlice = \"\";\n try {\n const raw = await readFile(memory.path, \"utf-8\");\n const frontmatterEnd = raw.indexOf(\"\\n---\", raw.indexOf(\"---\") + 3);\n fmSlice = frontmatterEnd > 0 ? raw.slice(0, frontmatterEnd) : raw;\n // Use matchAll to find ALL occurrences of `derived_via` / `derived_from`\n // in the raw YAML. `parseFrontmatter` keeps the LAST assignment when\n // duplicate keys appear, so the doctor must read the last occurrence\n // to match what the storage reader actually uses (PR #634 review,\n // codex P2 — duplicate `derived_via` keys caused false-clean or\n // false-unknown-operator warnings depending on order).\n const viaMatches = [...fmSlice.matchAll(new RegExp(DERIVED_VIA_RAW_RE.source, DERIVED_VIA_RAW_RE.flags + \"g\"))];\n viaMatchCount = viaMatches.length;\n duplicateViaKeys = viaMatches.length > 1;\n if (viaMatches.length > 0) {\n rawDerivedViaKeyPresent = true;\n // Use the last occurrence — `parseFrontmatter` keeps the last\n // assignment when duplicate keys appear, so the doctor must\n // match that behavior to produce accurate warnings (PR #634\n // review, codex P2).\n const lastVia = viaMatches[viaMatches.length - 1];\n let val = lastVia[1].trim();\n if (\n (val.startsWith('\"') && val.endsWith('\"')) ||\n (val.startsWith(\"'\") && val.endsWith(\"'\"))\n ) {\n val = val.slice(1, -1);\n }\n rawDerivedVia = val;\n }\n const fromMatches = [...fmSlice.matchAll(new RegExp(DERIVED_FROM_RAW_RE.source, DERIVED_FROM_RAW_RE.flags + \"g\"))];\n fromMatchCount = fromMatches.length;\n duplicateFromKeys = fromMatches.length > 1;\n if (fromMatches.length > 0) {\n rawDerivedFromKeyPresent = true;\n const lastFrom = fromMatches[fromMatches.length - 1];\n rawDerivedFrom = lastFrom[1].trim();\n }\n } catch {\n // Fall through to the parsed values.\n }\n\n const hasFrom = Array.isArray(derivedFrom) && derivedFrom.length > 0;\n const hasVia = derivedVia !== undefined && derivedVia !== null;\n const hasRawVia = rawDerivedVia !== undefined && rawDerivedVia.length > 0;\n // A raw `derived_from` that the parser dropped indicates on-disk\n // corruption we must surface. We detect this by: (a) the raw YAML\n // contains a `derived_from:` key, AND (b) the parsed frontmatter\n // has no valid array. A scalar like `derived_from: facts/a.md:7`\n // (list brackets omitted) or a blank `derived_from:` both hit this\n // branch.\n const hasRawMalformedFrom = rawDerivedFromKeyPresent && !hasFrom;\n // A blank `derived_via:` with no value is also corrupt — the\n // parser drops it to undefined, but the raw key is still present\n // on disk (PR #634 round-3 review, codex P2).\n const hasBlankRawVia =\n rawDerivedViaKeyPresent &&\n (rawDerivedVia === undefined || rawDerivedVia.length === 0) &&\n !hasVia;\n if (\n !hasFrom && !hasVia && !hasRawVia &&\n !hasRawMalformedFrom && !hasBlankRawVia\n ) continue;\n report.withProvenance += 1;\n\n // Duplicate-key detection (PR #634 review, codex P2): when the raw\n // YAML contains multiple `derived_via` or `derived_from` lines,\n // `parseFrontmatter` silently uses the last one. Flag this as a\n // malformed entry so operators can inspect and fix the file.\n if (duplicateViaKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `raw YAML contains ${viaMatchCount} \"derived_via\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n if (duplicateFromKeys) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML contains ${fromMatchCount} \"derived_from\" keys; parseFrontmatter uses the last occurrence`,\n });\n }\n\n if (hasRawMalformedFrom) {\n const display = rawDerivedFrom ?? \"(blank)\";\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML \"derived_from: ${display}\" could not be parsed as a list`,\n });\n }\n\n // Mixed-list detection (PR #634 round-4 + round-5 review, codex\n // P2): when the parser DID return a valid list but the raw YAML\n // includes additional tokens that got dropped, flag those as\n // malformed. Handles both flow-style (`[\"a\", \"\", \"b\"]`) and\n // block-style (`\\n - a\\n - \\n - b`) YAML lists.\n if (hasFrom && rawDerivedFromKeyPresent) {\n let rawList: string[] | null = null;\n if (rawDerivedFrom && rawDerivedFrom.length > 0) {\n rawList = tokenizeRawFlowList(rawDerivedFrom);\n }\n if (rawList === null) {\n // Fall back to block-list tokenization by re-reading the full\n // frontmatter (already loaded above as `raw`) and scanning\n // the lines following `derived_from:`.\n rawList = tokenizeRawBlockList(fmSlice, \"derived_from\");\n }\n if (rawList !== null && rawList.length > derivedFrom!.length) {\n for (const tok of rawList) {\n if (tok.length === 0) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains an empty entry (mixed list)`,\n });\n continue;\n }\n if (!derivedFrom!.includes(tok)) {\n // Accept either the snapshot format `<path>:<version>` or\n // a bare memory id (issue #687 PR 2/4 — pattern\n // reinforcement uses ID-shaped entries). PR #730\n // review feedback, Codex P2.\n if (\n !/^(.+):(\\d+)$/u.test(tok) &&\n !DERIVED_FROM_MEMORY_ID_RE.test(tok)\n ) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `raw YAML derived_from contains a malformed entry: ${JSON.stringify(tok)}`,\n });\n }\n }\n }\n }\n }\n if (hasBlankRawVia) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: \"raw YAML has `derived_via:` key with empty value\",\n });\n }\n\n if (hasFrom) {\n for (const entry of derivedFrom!) {\n // Pattern-reinforcement (issue #687 PR 2/4) records source\n // memory IDs directly in `derived_from` rather than\n // page-versioning snapshot references. Memory IDs may\n // contain `:` for namespace-prefixed forms like\n // `global:fact-abc-123`, but never `/` or `.` — those remain\n // exclusive to snapshot paths (PR #730 review feedback,\n // Codex P1). For ID-shaped entries we skip the snapshot\n // file check entirely — but ONLY when the operator is\n // `pattern-reinforcement`, which is the sole operator that\n // legitimately stores IDs rather than snapshot references.\n // Allowing the bypass for split/merge/update would weaken\n // validation on those existing consolidation paths (PR #730\n // review, Codex P2).\n if (\n derivedVia === \"pattern-reinforcement\" &&\n DERIVED_FROM_MEMORY_ID_RE.test(entry)\n ) {\n continue;\n }\n const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);\n if (!resolved.ok) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_malformed_entry\",\n detail: `${JSON.stringify(entry)}: ${resolved.reason}`,\n });\n continue;\n }\n // Require a regular file at the snapshot path (PR #634\n // round-8 review, codex P2) — a directory or device node at\n // that path means the sidecar was corrupted and the snapshot\n // is effectively missing.\n let snapshotOk = false;\n try {\n const st = await stat(resolved.snapshotPath);\n snapshotOk = st.isFile();\n } catch {\n snapshotOk = false;\n }\n if (!snapshotOk) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_from_missing_snapshot\",\n detail: `${entry} → ${resolved.snapshotPath} (not a regular file)`,\n });\n }\n }\n }\n\n // Check the RAW YAML value for unknown operators. The parsed value\n // (`fm.derived_via`) is always known-good because the read-path\n // normalizer dropped anything else to undefined.\n if (hasRawVia && !isConsolidationOperator(rawDerivedVia)) {\n report.issues.push({\n memoryPath: memory.path,\n memoryId: fm.id,\n kind: \"derived_via_unknown_operator\",\n detail: `unknown operator: ${JSON.stringify(rawDerivedVia)}`,\n });\n }\n }\n\n // Parse-failure detection (PR #634 round-4 review, codex P2):\n // `readAllMemories()` silently drops files whose frontmatter\n // doesn't parse. Walk every recall category directory for `.md` files\n // that DO reference provenance frontmatter but didn't come back from the\n // reader — those are the corruption cases the doctor is meant to surface.\n // Uses RECALL_FALLBACK_DIRS (the single source of truth) so newly-routed\n // categories (decisions/, preferences/, ...) are scanned too (#1546).\n try {\n const seenPaths = new Set(memories.map((m) => m.path));\n for (const rootName of RECALL_FALLBACK_DIRS) {\n const rootPath = path.join(memoryDir, rootName);\n for await (const file of walkMarkdownFiles(rootPath, memoryDir)) {\n if (seenPaths.has(file)) continue;\n try {\n const raw = await readFile(file, \"utf-8\");\n if (\n DERIVED_FROM_RAW_RE.test(raw) ||\n DERIVED_VIA_RAW_RE.test(raw)\n ) {\n report.withProvenance += 1;\n report.issues.push({\n memoryPath: file,\n memoryId: \"(parse failed)\",\n kind: \"derived_from_malformed_entry\",\n detail:\n \"frontmatter could not be parsed by storage reader; provenance fields visible in raw YAML\",\n });\n }\n } catch {\n // Unreadable file — skip.\n }\n }\n }\n } catch {\n // Best-effort; don't fail the whole scan on a filesystem hiccup.\n }\n\n return report;\n}\n\n/**\n * Recursively yield all `.md` file paths under `root`. Silent on\n * missing directories — the facts/corrections dirs may not exist in\n * fresh installs. Symlinked roots/directories are skipped so the\n * best-effort parse-failure pass cannot escape `memoryDir`.\n */\nasync function* walkMarkdownFiles(root: string, memoryDir: string): AsyncGenerator<string> {\n let entries;\n let memoryDirReal: string;\n try {\n const rootStat = await lstat(root);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) return;\n memoryDirReal = await realpath(memoryDir);\n const rootReal = await realpath(root);\n if (!isPathWithin(rootReal, memoryDirReal)) return;\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(root, entry.name);\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n yield* walkMarkdownFiles(full, memoryDirReal);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n try {\n const fileReal = await realpath(full);\n if (!isPathWithin(fileReal, memoryDirReal)) continue;\n } catch {\n continue;\n }\n yield full;\n }\n }\n}\n\nfunction isPathWithin(candidate: string, root: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === \"\" || (!!relative && !relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":"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","names":[]}