@remnic/core 9.63.6 → 9.63.8

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 (304) hide show
  1. package/dist/access-admin-ops-surface.d.ts +3 -2
  2. package/dist/access-admin-ops-surface.js +31 -27
  3. package/dist/access-authorization-probe.d.ts +3 -2
  4. package/dist/access-authorization-probe.js +32 -28
  5. package/dist/access-boundary.d.ts +3 -2
  6. package/dist/access-boundary.js +31 -27
  7. package/dist/access-cli.js +98 -95
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +3 -2
  10. package/dist/access-extraction-force-flush.js +31 -27
  11. package/dist/access-http-lcm-compaction.d.ts +3 -2
  12. package/dist/access-http-lifecycle-flush.d.ts +3 -2
  13. package/dist/access-http-offline-stream.d.ts +3 -2
  14. package/dist/access-http-query.js +33 -29
  15. package/dist/access-http.d.ts +4 -3
  16. package/dist/access-http.js +47 -43
  17. package/dist/access-identity-continuity-surface.d.ts +2 -1
  18. package/dist/access-identity-continuity-surface.js +31 -27
  19. package/dist/access-lcm-surface.d.ts +3 -2
  20. package/dist/access-lcm-surface.js +31 -27
  21. package/dist/access-mcp-output-schemas.js +1 -1
  22. package/dist/access-mcp.d.ts +3 -2
  23. package/dist/access-mcp.js +38 -34
  24. package/dist/access-namespace-preflight.js +31 -27
  25. package/dist/access-observe-write-surface.d.ts +3 -2
  26. package/dist/access-observe-write-surface.js +31 -27
  27. package/dist/access-offline-manifest.d.ts +2 -1
  28. package/dist/access-offline-manifest.js +5 -4
  29. package/dist/access-operations-batch.js +32 -28
  30. package/dist/access-operations.d.ts +3 -2
  31. package/dist/access-operations.js +36 -32
  32. package/dist/access-recall-concurrency.d.ts +3 -2
  33. package/dist/access-recall-concurrency.js +31 -27
  34. package/dist/access-recall-response.d.ts +3 -2
  35. package/dist/access-recall-response.js +31 -27
  36. package/dist/access-recall-surface.d.ts +3 -2
  37. package/dist/access-recall-surface.js +31 -27
  38. package/dist/access-schema.js +6 -5
  39. package/dist/{access-service-cKQrte9Z.d.ts → access-service-BaPa_km6.d.ts} +1 -1
  40. package/dist/access-service-helpers.d.ts +3 -2
  41. package/dist/access-service.d.ts +3 -2
  42. package/dist/access-service.js +31 -27
  43. package/dist/access-surface-catalog.d.ts +3 -2
  44. package/dist/access-surface-catalog.js +33 -29
  45. package/dist/access-surface-catalog.js.map +1 -1
  46. package/dist/active-recall.js +4 -2
  47. package/dist/active-recall.js.map +1 -1
  48. package/dist/adapters/index.js +4 -4
  49. package/dist/adapters/registry.js +2 -2
  50. package/dist/{auto-sync-BKRJDMKX.js → auto-sync-3SHMZVQS.js} +5 -3
  51. package/dist/{auto-sync-BKRJDMKX.js.map → auto-sync-3SHMZVQS.js.map} +1 -1
  52. package/dist/bootstrap.d.ts +2 -1
  53. package/dist/briefing.js +10 -10
  54. package/dist/calibration.d.ts +2 -1
  55. package/dist/calibration.js +7 -0
  56. package/dist/calibration.js.map +1 -1
  57. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  58. package/dist/causal-consolidation.js +11 -11
  59. package/dist/census-validation.d.ts +19 -0
  60. package/dist/census-validation.js +14 -0
  61. package/dist/census-validation.js.map +1 -0
  62. package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
  63. package/dist/chunk-2YA5N2BY.js +65 -0
  64. package/dist/chunk-2YA5N2BY.js.map +1 -0
  65. package/dist/{chunk-PB4WVYGC.js → chunk-3IMYRX3W.js} +2 -2
  66. package/dist/{chunk-R6UMNP5C.js → chunk-3T6HIIKA.js} +2 -2
  67. package/dist/{chunk-77QZW2D6.js → chunk-3TDAA7VA.js} +2 -2
  68. package/dist/{chunk-TWTUGCTY.js → chunk-3YTTAGPB.js} +6 -1
  69. package/dist/chunk-3YTTAGPB.js.map +1 -0
  70. package/dist/{chunk-DXDAQZ2K.js → chunk-42TIIO2M.js} +2 -2
  71. package/dist/{chunk-Y7COCXY2.js → chunk-4F67JJPG.js} +2 -2
  72. package/dist/{chunk-OW7NVMGQ.js → chunk-5OOTHWGJ.js} +2 -2
  73. package/dist/{chunk-KYTRF64R.js → chunk-5SCFDQEJ.js} +2 -2
  74. package/dist/{chunk-DYZR3Z6D.js → chunk-5TF5STAV.js} +4 -4
  75. package/dist/{chunk-GG4DN63W.js → chunk-62Y5AGCM.js} +2 -2
  76. package/dist/{chunk-PMVPCZJZ.js → chunk-6433HV72.js} +2 -2
  77. package/dist/{chunk-KHCUVYCY.js → chunk-6UKSOXAO.js} +3 -3
  78. package/dist/{chunk-GF7RQWXC.js → chunk-AJRJDXYR.js} +2 -2
  79. package/dist/{chunk-YEQCMA5L.js → chunk-BOSIHHOH.js} +2 -2
  80. package/dist/{chunk-IVXVLDNT.js → chunk-BPQ7L27U.js} +77 -92
  81. package/dist/chunk-BPQ7L27U.js.map +1 -0
  82. package/dist/{chunk-7LF2OHIJ.js → chunk-BRHKW7GY.js} +2 -2
  83. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  84. package/dist/{chunk-LX4TNSAE.js → chunk-CBY54QYW.js} +4 -4
  85. package/dist/{chunk-CIAI24TQ.js → chunk-DGUVXXAU.js} +3 -3
  86. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  87. package/dist/{chunk-QI5RDDEL.js → chunk-EDZNW3HE.js} +3 -3
  88. package/dist/chunk-FEHIOYML.js +168 -0
  89. package/dist/chunk-FEHIOYML.js.map +1 -0
  90. package/dist/{chunk-Z4N7SVVF.js → chunk-FZEEWOI2.js} +9 -4
  91. package/dist/chunk-FZEEWOI2.js.map +1 -0
  92. package/dist/{chunk-6YMEU6OQ.js → chunk-G72IPMR7.js} +2 -2
  93. package/dist/{chunk-7AF243WZ.js → chunk-GBVRVYF4.js} +1 -1
  94. package/dist/{chunk-FPEZ74F7.js → chunk-I2HTKOOP.js} +3 -3
  95. package/dist/{chunk-R7TZ2HCS.js → chunk-JG22O53B.js} +58 -3
  96. package/dist/chunk-JG22O53B.js.map +1 -0
  97. package/dist/{chunk-VPHSMJ2E.js → chunk-JPWSYHLB.js} +2 -2
  98. package/dist/{chunk-X7LEDA74.js → chunk-KFB4YNXM.js} +2 -2
  99. package/dist/{chunk-I5FLIB2J.js → chunk-KXWE62IH.js} +2 -2
  100. package/dist/chunk-LQWOFU6L.js +17 -0
  101. package/dist/chunk-LQWOFU6L.js.map +1 -0
  102. package/dist/{chunk-TSTI6A3A.js → chunk-LTYH2L6E.js} +13 -13
  103. package/dist/{chunk-ZUPKNWRZ.js → chunk-MIUL6AUZ.js} +3 -3
  104. package/dist/{chunk-EJY3HJMT.js → chunk-MYI3RKI6.js} +2 -2
  105. package/dist/{chunk-UCLMKNHA.js → chunk-N7V3P4V5.js} +2 -2
  106. package/dist/{chunk-NA56NSKL.js → chunk-O52TB5SL.js} +2 -2
  107. package/dist/{chunk-7KQB4EBS.js → chunk-OPPBTPUU.js} +4 -4
  108. package/dist/{chunk-ERPBRXVZ.js → chunk-PHKU6BZ2.js} +2 -2
  109. package/dist/{chunk-GTD4Q62X.js → chunk-PQHNZ63P.js} +4 -4
  110. package/dist/{chunk-FHKXVTM5.js → chunk-Q5XKKDFQ.js} +16 -16
  111. package/dist/{chunk-QRWMS6SI.js → chunk-Q6OL23WM.js} +2 -2
  112. package/dist/{chunk-V3KIRICY.js → chunk-QQYHYW2N.js} +3 -3
  113. package/dist/{chunk-YFCEBB5P.js → chunk-QTXNS3HB.js} +2 -2
  114. package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
  115. package/dist/{chunk-E4KMY4KR.js → chunk-SNEUAZ6B.js} +2 -2
  116. package/dist/{chunk-F6JYBVL6.js → chunk-SY5JUJ2X.js} +52 -52
  117. package/dist/{chunk-EFKZT6AY.js → chunk-T42L3S6A.js} +6 -11
  118. package/dist/chunk-T42L3S6A.js.map +1 -0
  119. package/dist/{chunk-YXMHUBN2.js → chunk-TOP3FSDW.js} +25 -102
  120. package/dist/chunk-TOP3FSDW.js.map +1 -0
  121. package/dist/{chunk-PBRCYYO7.js → chunk-ULZFZL7K.js} +2 -2
  122. package/dist/{chunk-YYQAKIDJ.js → chunk-VD2OT77P.js} +2 -2
  123. package/dist/chunk-VD2OT77P.js.map +1 -0
  124. package/dist/{chunk-IPLYGWQF.js → chunk-VDX2J7OX.js} +6 -6
  125. package/dist/{chunk-UGHCZS6K.js → chunk-VFZQXYN5.js} +2 -2
  126. package/dist/{chunk-NKB43PGI.js → chunk-VYMICVRA.js} +2 -2
  127. package/dist/{chunk-OETHJBAL.js → chunk-WBPD6SBZ.js} +109 -116
  128. package/dist/chunk-WBPD6SBZ.js.map +1 -0
  129. package/dist/{chunk-2UM6WHME.js → chunk-XE2QCCT2.js} +2 -2
  130. package/dist/{chunk-SYVGVK4S.js → chunk-XTMYB2HY.js} +2 -2
  131. package/dist/{chunk-AYVCKGA6.js → chunk-YTXNDEUS.js} +2 -2
  132. package/dist/{chunk-3Z5TGRFA.js → chunk-ZOAAY2OE.js} +9 -9
  133. package/dist/{chunk-L3CHRBHE.js → chunk-ZRUEP4IK.js} +17 -11
  134. package/dist/chunk-ZRUEP4IK.js.map +1 -0
  135. package/dist/{chunk-PRDJMOST.js → chunk-ZVZHG7AI.js} +65 -15
  136. package/dist/chunk-ZVZHG7AI.js.map +1 -0
  137. package/dist/{chunk-RYST72N5.js → chunk-ZWYXSF2T.js} +4 -154
  138. package/dist/chunk-ZWYXSF2T.js.map +1 -0
  139. package/dist/{cli-BFBDYV0D.d.ts → cli-D0tV7eeU.d.ts} +2 -2
  140. package/dist/cli.d.ts +4 -3
  141. package/dist/cli.js +69 -65
  142. package/dist/compounding/engine.js +10 -10
  143. package/dist/config.js +4 -2
  144. package/dist/connectors/codex-materialize-runner.js +10 -10
  145. package/dist/connectors/index.js +10 -10
  146. package/dist/consolidation-provenance-check.js +2 -2
  147. package/dist/convergence-refresh.js +12 -12
  148. package/dist/conversation-index/backend.js +2 -2
  149. package/dist/corpus-watermark.js +12 -12
  150. package/dist/entity-retrieval.js +10 -10
  151. package/dist/explicit-capture.d.ts +2 -1
  152. package/dist/external-wiki-access.d.ts +3 -2
  153. package/dist/external-wiki-access.js +32 -28
  154. package/dist/external-wiki-mcp-tools.d.ts +3 -2
  155. package/dist/extraction-judge.d.ts +2 -1
  156. package/dist/extraction-judge.js +6 -1
  157. package/dist/extraction-normalization.js +2 -2
  158. package/dist/extraction-prompt.d.ts +20 -1
  159. package/dist/extraction-prompt.js +11 -1
  160. package/dist/extraction.js +13 -13
  161. package/dist/harmonic-retrieval.js +1 -1
  162. package/dist/included-memories.d.ts +11 -0
  163. package/dist/included-memories.js +14 -0
  164. package/dist/included-memories.js.map +1 -0
  165. package/dist/index.d.ts +6 -5
  166. package/dist/index.js +117 -114
  167. package/dist/lcm/index.js +3 -3
  168. package/dist/maintenance/memory-governance.js +10 -10
  169. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  170. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  171. package/dist/mcp-memory-inspector-app.d.ts +3 -2
  172. package/dist/namespaces/migrate.js +11 -11
  173. package/dist/namespaces/search.js +4 -4
  174. package/dist/namespaces/storage.js +10 -10
  175. package/dist/offline-sync.js +2 -1
  176. package/dist/operator-doctor-corpus.js +13 -13
  177. package/dist/operator-toolkit.js +23 -21
  178. package/dist/orchestration/maintenance.js +16 -16
  179. package/dist/{orchestrator-CSc9GfQv.d.ts → orchestrator-Bay_AofR.d.ts} +2 -3
  180. package/dist/orchestrator.d.ts +2 -1
  181. package/dist/orchestrator.js +100 -97
  182. package/dist/{public-http-8PcrDIYi.d.ts → public-http-BtjiyKah.d.ts} +1 -1
  183. package/dist/recall-explain-renderer.d.ts +1 -0
  184. package/dist/recall-state.d.ts +6 -2
  185. package/dist/recall-state.js +6 -2
  186. package/dist/reconcile/cursor.js +7 -3
  187. package/dist/reconcile/cursor.js.map +1 -1
  188. package/dist/reconcile/manifest.js +3 -7
  189. package/dist/reconcile/plan.d.ts +15 -1
  190. package/dist/reconcile/plan.js +6 -10
  191. package/dist/resume-bundles.js +5 -3
  192. package/dist/search/factory.js +3 -3
  193. package/dist/search/index.js +3 -3
  194. package/dist/semantic-consolidation.js +16 -16
  195. package/dist/semantic-rule-promotion.js +10 -10
  196. package/dist/semantic-rule-verifier.js +10 -10
  197. package/dist/source-agent-qualifier.js +10 -10
  198. package/dist/storage.js +9 -9
  199. package/dist/support-passport/index.d.ts +6 -5
  200. package/dist/support-passport/index.js +34 -30
  201. package/dist/transfer/autodetect.js +1 -1
  202. package/dist/transfer/backup.js +2 -2
  203. package/dist/transfer/capsule-export.js +3 -3
  204. package/dist/transfer/capsule-import.js +2 -2
  205. package/dist/verified-recall.js +10 -10
  206. package/package.json +2 -2
  207. package/src/access-mcp-output-schemas.ts +1 -1
  208. package/src/access-recall-response.ts +4 -2
  209. package/src/access-recall-surface.ts +9 -9
  210. package/src/access-service-namespace.test.ts +9 -9
  211. package/src/access-service-raw-excerpt-read-gate.test.ts +1 -1
  212. package/src/calibration.ts +3 -1
  213. package/src/census-validation.ts +25 -0
  214. package/src/coding/architecture-surfaces.test.ts +9 -10
  215. package/src/coding/architecture-surfaces.ts +2 -35
  216. package/src/coding/codegraph-runtime-loader.test.ts +228 -0
  217. package/src/coding/codegraph-runtime.ts +30 -50
  218. package/src/coding/codegraph-surfaces.ts +2 -2
  219. package/src/coding/coding-knowledge-config.ts +35 -1
  220. package/src/coding/decision-surfaces.test.ts +9 -10
  221. package/src/coding/decision-surfaces.ts +2 -40
  222. package/src/coding/session-delta-surfaces.test.ts +9 -10
  223. package/src/coding/session-delta-surfaces.ts +2 -33
  224. package/src/extraction-judge.ts +3 -1
  225. package/src/extraction-output-language.test.ts +116 -0
  226. package/src/extraction-prompt.ts +70 -1
  227. package/src/extraction.ts +23 -95
  228. package/src/included-memories.ts +88 -0
  229. package/src/offline-sync.ts +4 -4
  230. package/src/orchestration/recall-internal-deps.ts +2 -4
  231. package/src/orchestration/recall-internal.ts +9 -13
  232. package/src/orchestration/recall-result-formatter-display-safe.test.ts +13 -16
  233. package/src/orchestration/recall-result-formatter.ts +32 -45
  234. package/src/orchestration/recall-section-coordinator.ts +20 -26
  235. package/src/orchestrator.ts +2 -4
  236. package/src/recall-explain-renderer.test.ts +4 -0
  237. package/src/recall-state.test.ts +108 -5
  238. package/src/recall-state.ts +85 -23
  239. package/src/recall-token-budget.test.ts +1 -1
  240. package/src/reconcile/census-parity.test.ts +152 -0
  241. package/src/reconcile/cursor.ts +5 -5
  242. package/src/reconcile/manifest.ts +4 -11
  243. package/src/reconcile/plan.ts +18 -15
  244. package/dist/chunk-EFKZT6AY.js.map +0 -1
  245. package/dist/chunk-IVXVLDNT.js.map +0 -1
  246. package/dist/chunk-L3CHRBHE.js.map +0 -1
  247. package/dist/chunk-OETHJBAL.js.map +0 -1
  248. package/dist/chunk-PRDJMOST.js.map +0 -1
  249. package/dist/chunk-R7TZ2HCS.js.map +0 -1
  250. package/dist/chunk-RYST72N5.js.map +0 -1
  251. package/dist/chunk-TWTUGCTY.js.map +0 -1
  252. package/dist/chunk-YXMHUBN2.js.map +0 -1
  253. package/dist/chunk-YYQAKIDJ.js.map +0 -1
  254. package/dist/chunk-Z4N7SVVF.js.map +0 -1
  255. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  256. /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
  257. /package/dist/{chunk-PB4WVYGC.js.map → chunk-3IMYRX3W.js.map} +0 -0
  258. /package/dist/{chunk-R6UMNP5C.js.map → chunk-3T6HIIKA.js.map} +0 -0
  259. /package/dist/{chunk-77QZW2D6.js.map → chunk-3TDAA7VA.js.map} +0 -0
  260. /package/dist/{chunk-DXDAQZ2K.js.map → chunk-42TIIO2M.js.map} +0 -0
  261. /package/dist/{chunk-Y7COCXY2.js.map → chunk-4F67JJPG.js.map} +0 -0
  262. /package/dist/{chunk-OW7NVMGQ.js.map → chunk-5OOTHWGJ.js.map} +0 -0
  263. /package/dist/{chunk-KYTRF64R.js.map → chunk-5SCFDQEJ.js.map} +0 -0
  264. /package/dist/{chunk-DYZR3Z6D.js.map → chunk-5TF5STAV.js.map} +0 -0
  265. /package/dist/{chunk-GG4DN63W.js.map → chunk-62Y5AGCM.js.map} +0 -0
  266. /package/dist/{chunk-PMVPCZJZ.js.map → chunk-6433HV72.js.map} +0 -0
  267. /package/dist/{chunk-KHCUVYCY.js.map → chunk-6UKSOXAO.js.map} +0 -0
  268. /package/dist/{chunk-GF7RQWXC.js.map → chunk-AJRJDXYR.js.map} +0 -0
  269. /package/dist/{chunk-YEQCMA5L.js.map → chunk-BOSIHHOH.js.map} +0 -0
  270. /package/dist/{chunk-7LF2OHIJ.js.map → chunk-BRHKW7GY.js.map} +0 -0
  271. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  272. /package/dist/{chunk-LX4TNSAE.js.map → chunk-CBY54QYW.js.map} +0 -0
  273. /package/dist/{chunk-CIAI24TQ.js.map → chunk-DGUVXXAU.js.map} +0 -0
  274. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  275. /package/dist/{chunk-QI5RDDEL.js.map → chunk-EDZNW3HE.js.map} +0 -0
  276. /package/dist/{chunk-6YMEU6OQ.js.map → chunk-G72IPMR7.js.map} +0 -0
  277. /package/dist/{chunk-7AF243WZ.js.map → chunk-GBVRVYF4.js.map} +0 -0
  278. /package/dist/{chunk-FPEZ74F7.js.map → chunk-I2HTKOOP.js.map} +0 -0
  279. /package/dist/{chunk-VPHSMJ2E.js.map → chunk-JPWSYHLB.js.map} +0 -0
  280. /package/dist/{chunk-X7LEDA74.js.map → chunk-KFB4YNXM.js.map} +0 -0
  281. /package/dist/{chunk-I5FLIB2J.js.map → chunk-KXWE62IH.js.map} +0 -0
  282. /package/dist/{chunk-TSTI6A3A.js.map → chunk-LTYH2L6E.js.map} +0 -0
  283. /package/dist/{chunk-ZUPKNWRZ.js.map → chunk-MIUL6AUZ.js.map} +0 -0
  284. /package/dist/{chunk-EJY3HJMT.js.map → chunk-MYI3RKI6.js.map} +0 -0
  285. /package/dist/{chunk-UCLMKNHA.js.map → chunk-N7V3P4V5.js.map} +0 -0
  286. /package/dist/{chunk-NA56NSKL.js.map → chunk-O52TB5SL.js.map} +0 -0
  287. /package/dist/{chunk-7KQB4EBS.js.map → chunk-OPPBTPUU.js.map} +0 -0
  288. /package/dist/{chunk-ERPBRXVZ.js.map → chunk-PHKU6BZ2.js.map} +0 -0
  289. /package/dist/{chunk-GTD4Q62X.js.map → chunk-PQHNZ63P.js.map} +0 -0
  290. /package/dist/{chunk-FHKXVTM5.js.map → chunk-Q5XKKDFQ.js.map} +0 -0
  291. /package/dist/{chunk-QRWMS6SI.js.map → chunk-Q6OL23WM.js.map} +0 -0
  292. /package/dist/{chunk-V3KIRICY.js.map → chunk-QQYHYW2N.js.map} +0 -0
  293. /package/dist/{chunk-YFCEBB5P.js.map → chunk-QTXNS3HB.js.map} +0 -0
  294. /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
  295. /package/dist/{chunk-E4KMY4KR.js.map → chunk-SNEUAZ6B.js.map} +0 -0
  296. /package/dist/{chunk-F6JYBVL6.js.map → chunk-SY5JUJ2X.js.map} +0 -0
  297. /package/dist/{chunk-PBRCYYO7.js.map → chunk-ULZFZL7K.js.map} +0 -0
  298. /package/dist/{chunk-IPLYGWQF.js.map → chunk-VDX2J7OX.js.map} +0 -0
  299. /package/dist/{chunk-UGHCZS6K.js.map → chunk-VFZQXYN5.js.map} +0 -0
  300. /package/dist/{chunk-NKB43PGI.js.map → chunk-VYMICVRA.js.map} +0 -0
  301. /package/dist/{chunk-2UM6WHME.js.map → chunk-XE2QCCT2.js.map} +0 -0
  302. /package/dist/{chunk-SYVGVK4S.js.map → chunk-XTMYB2HY.js.map} +0 -0
  303. /package/dist/{chunk-AYVCKGA6.js.map → chunk-YTXNDEUS.js.map} +0 -0
  304. /package/dist/{chunk-3Z5TGRFA.js.map → chunk-ZOAAY2OE.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-B7RWBZXW.js";
4
4
  import {
5
5
  StorageManager
6
- } from "./chunk-FHKXVTM5.js";
6
+ } from "./chunk-Q5XKKDFQ.js";
7
7
  import {
8
8
  getCachedEpisodeMap,
9
9
  setCachedEpisodeMap
@@ -109,4 +109,4 @@ export {
109
109
  compareVerifiedEpisodeResults,
110
110
  searchVerifiedEpisodes
111
111
  };
112
- //# sourceMappingURL=chunk-2UM6WHME.js.map
112
+ //# sourceMappingURL=chunk-XE2QCCT2.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  rebuildMemoryProjection
3
- } from "./chunk-KHCUVYCY.js";
3
+ } from "./chunk-6UKSOXAO.js";
4
4
 
5
5
  // src/convergence-refresh.ts
6
6
  async function refreshConvergedNamespaces(namespaces, namespaceSearchRouter, getStorage, searchEnabled) {
@@ -32,4 +32,4 @@ async function refreshConvergedNamespaces(namespaces, namespaceSearchRouter, get
32
32
  export {
33
33
  refreshConvergedNamespaces
34
34
  };
35
- //# sourceMappingURL=chunk-SYVGVK4S.js.map
35
+ //# sourceMappingURL=chunk-XTMYB2HY.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  StorageManager
3
- } from "./chunk-FHKXVTM5.js";
3
+ } from "./chunk-Q5XKKDFQ.js";
4
4
  import {
5
5
  normalizeEntityName
6
6
  } from "./chunk-YXZG5TVN.js";
@@ -956,4 +956,4 @@ export {
956
956
  resolveBriefingSaveDir,
957
957
  briefingFilename
958
958
  };
959
- //# sourceMappingURL=chunk-AYVCKGA6.js.map
959
+ //# sourceMappingURL=chunk-YTXNDEUS.js.map
@@ -3,7 +3,7 @@ import {
3
3
  readNamespaceMaintenanceLastRanStatuses,
4
4
  readNamespaceMaintenanceStatuses,
5
5
  runNamespaceMaintenancePlan
6
- } from "./chunk-V3KIRICY.js";
6
+ } from "./chunk-QQYHYW2N.js";
7
7
  import {
8
8
  analyzeSessionIntegrity,
9
9
  applySessionRepair,
@@ -14,7 +14,7 @@ import {
14
14
  } from "./chunk-CMTINOFS.js";
15
15
  import {
16
16
  summarizeCorpusAndReplica
17
- } from "./chunk-UGHCZS6K.js";
17
+ } from "./chunk-VFZQXYN5.js";
18
18
  import {
19
19
  readJudgeVerdictStats
20
20
  } from "./chunk-OZKOPQCM.js";
@@ -32,29 +32,29 @@ import {
32
32
  } from "./chunk-2DQ2XREU.js";
33
33
  import {
34
34
  runConsolidationProvenanceCheck
35
- } from "./chunk-7KQB4EBS.js";
35
+ } from "./chunk-OPPBTPUU.js";
36
36
  import {
37
37
  reportBufferSurpriseDistribution
38
38
  } from "./chunk-YBPYIAA5.js";
39
39
  import {
40
40
  parseConfig
41
- } from "./chunk-RYST72N5.js";
41
+ } from "./chunk-ZWYXSF2T.js";
42
42
  import {
43
43
  readAggregateExtractionWatermark
44
- } from "./chunk-YFCEBB5P.js";
44
+ } from "./chunk-QTXNS3HB.js";
45
45
  import {
46
46
  listMemoryGovernanceRuns,
47
47
  readMemoryGovernanceRunArtifact
48
- } from "./chunk-77QZW2D6.js";
48
+ } from "./chunk-3TDAA7VA.js";
49
49
  import {
50
50
  listNamespaces
51
- } from "./chunk-ZUPKNWRZ.js";
51
+ } from "./chunk-MIUL6AUZ.js";
52
52
  import {
53
53
  analyzeGraphHealth
54
54
  } from "./chunk-YMOPYG3K.js";
55
55
  import {
56
56
  StorageManager
57
- } from "./chunk-FHKXVTM5.js";
57
+ } from "./chunk-Q5XKKDFQ.js";
58
58
  import {
59
59
  lintWorkspaceFiles
60
60
  } from "./chunk-DM2T26WE.js";
@@ -1683,4 +1683,4 @@ export {
1683
1683
  runBenchmarkRecall,
1684
1684
  runOperatorRepair
1685
1685
  };
1686
- //# sourceMappingURL=chunk-3Z5TGRFA.js.map
1686
+ //# sourceMappingURL=chunk-ZOAAY2OE.js.map
@@ -2,12 +2,14 @@ import {
2
2
  CONVERGE_CONFLICT_POLICIES,
3
3
  DEFAULT_CONVERGE_CONFLICT_POLICY
4
4
  } from "./chunk-HBVPYRDW.js";
5
- import {
6
- OFFLINE_SYNC_MAX_MTIME_MS
7
- } from "./chunk-Z4N7SVVF.js";
8
5
  import {
9
6
  validateArchiveRelativePath
10
7
  } from "./chunk-GCGJW34D.js";
8
+ import {
9
+ CENSUS_MAX_MTIME_MS,
10
+ isCensusMtimeMs,
11
+ isSha256Hex
12
+ } from "./chunk-LQWOFU6L.js";
11
13
  import {
12
14
  normalizeNamespaceIdentity
13
15
  } from "./chunk-VUFL7X3H.js";
@@ -19,7 +21,6 @@ var ReconcilePlanInputError = class extends Error {
19
21
  this.name = "ReconcilePlanInputError";
20
22
  }
21
23
  };
22
- var SHA256_PATTERN = /^[a-f0-9]{64}$/i;
23
24
  function assertCensusRecord(file, side, namespace) {
24
25
  const path = file?.path;
25
26
  if (typeof path !== "string" || path.length === 0) {
@@ -41,15 +42,15 @@ function assertCensusRecord(file, side, namespace) {
41
42
  };
42
43
  }
43
44
  function assertMtimeMs(value, context) {
44
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > OFFLINE_SYNC_MAX_MTIME_MS) {
45
+ if (!isCensusMtimeMs(value)) {
45
46
  throw new ReconcilePlanInputError(
46
- `reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${OFFLINE_SYNC_MAX_MTIME_MS}`
47
+ `reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${CENSUS_MAX_MTIME_MS}`
47
48
  );
48
49
  }
49
50
  return value;
50
51
  }
51
52
  function assertDigest(value, context) {
52
- if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
53
+ if (!isSha256Hex(value)) {
53
54
  throw new ReconcilePlanInputError(
54
55
  `reconcile: ${context} must carry a 64-character sha256 hex digest`
55
56
  );
@@ -188,11 +189,14 @@ function assertConflictPolicy(value) {
188
189
  }
189
190
  return value;
190
191
  }
191
- function compareEntries(a, b) {
192
+ function compareReconcilePlanEntries(a, b) {
192
193
  if (a.namespace !== b.namespace) return a.namespace < b.namespace ? -1 : 1;
193
194
  if (a.path !== b.path) return a.path < b.path ? -1 : 1;
194
195
  return 0;
195
196
  }
197
+ function semanticAgreementKey(agreement) {
198
+ return `${agreement.local.path}\0${agreement.peer.path}`;
199
+ }
196
200
  function resolveConflict(policy, localMtimeMs, peerMtimeMs, missingTimestampResolution) {
197
201
  if (policy !== "newest-wins") return "unresolved";
198
202
  if (localMtimeMs === void 0 || peerMtimeMs === void 0 || localMtimeMs === peerMtimeMs) {
@@ -396,7 +400,7 @@ function planNamespaceReconciliation(input, options = {}) {
396
400
  peerSha256: peerFile.sha256
397
401
  });
398
402
  }
399
- return entries.sort(compareEntries);
403
+ return entries.sort(compareReconcilePlanEntries);
400
404
  }
401
405
  function summarizeReconcilePlan(entries) {
402
406
  const byNamespace = /* @__PURE__ */ new Map();
@@ -432,7 +436,7 @@ function planReconciliation(namespaces, options = {}) {
432
436
  seenNamespaces.add(name);
433
437
  entries.push(...planNamespaceReconciliation(namespace, options));
434
438
  }
435
- entries.sort(compareEntries);
439
+ entries.sort(compareReconcilePlanEntries);
436
440
  return {
437
441
  entries,
438
442
  byNamespace: summarizeReconcilePlan(entries),
@@ -442,8 +446,10 @@ function planReconciliation(namespaces, options = {}) {
442
446
 
443
447
  export {
444
448
  ReconcilePlanInputError,
449
+ compareReconcilePlanEntries,
450
+ semanticAgreementKey,
445
451
  planNamespaceReconciliation,
446
452
  summarizeReconcilePlan,
447
453
  planReconciliation
448
454
  };
449
- //# sourceMappingURL=chunk-L3CHRBHE.js.map
455
+ //# sourceMappingURL=chunk-ZRUEP4IK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reconcile/plan.ts"],"sourcesContent":["import { CENSUS_MAX_MTIME_MS, isCensusMtimeMs, isSha256Hex } from \"../census-validation.js\";\nimport { normalizeNamespaceIdentity } from \"../namespaces/identity.js\";\nimport { validateArchiveRelativePath } from \"../transfer/fs-utils.js\";\nimport type { OfflineSyncFileState } from \"../offline-sync.js\";\nimport {\n CONVERGE_CONFLICT_POLICIES,\n DEFAULT_CONVERGE_CONFLICT_POLICY,\n} from \"../converge-config.js\";\nimport type { ConvergeConflictPolicy } from \"../types.js\";\nexport type ReconcileConflictPolicy = ConvergeConflictPolicy;\n\n/**\n * Bootstrap reconciliation planner for two peer daemons whose corpora have\n * already diverged (issue #2150).\n *\n * The offline-sync protocol assumes a satellite that shares a common base with\n * one daemon: every path it does not know about is a pull, and a conflict is\n * rare. Two long-lived daemons are the opposite case — each side holds months\n * of history the other never saw, both sides are authoritative, and there is\n * NO common base on the first run. That is a different decision problem, so it\n * gets its own planner rather than another mode flag inside `applyOfflineSync*`.\n *\n * This module is deliberately pure: it takes two file censuses and returns what\n * to do. No I/O, no transport, no clock. Everything that makes reconciliation\n * risky — which side wins, what counts as converged, what is reported — is\n * decided here where it can be exhaustively tested, and the transport layer is\n * left to carry out an already-settled plan.\n */\n\n/** Which way a path must move for the two corpora to agree. */\nexport type ReconcileAction = \"pull\" | \"push\" | \"identical\" | \"conflict\" | \"suppress\";\n\n/**\n * What the planner decided for a conflicting path.\n * `supersede-link` records that both revisions need durable preservation when\n * timestamps cannot safely select a winner. Apply must stop until transport\n * can assign distinct durable identities to both revisions.\n */\nexport type ReconcileResolution = \"local-wins\" | \"peer-wins\" | \"supersede-link\" | \"unresolved\";\n\nexport interface ReconcileSemanticFileState {\n path: string;\n sha256: string;\n}\n\nexport interface ReconcileSemanticAgreement {\n local: ReconcileSemanticFileState;\n peer: ReconcileSemanticFileState;\n}\n\nexport type ReconcileSemanticChange = \"unchanged\" | \"local_changed\" | \"peer_changed\" | \"both_modified\";\n\nexport interface ReconcilePlanEntry {\n path: string;\n namespace: string;\n action: ReconcileAction;\n /** Stable machine-readable cause, safe to assert on and to aggregate. */\n reason: ReconcileReason;\n localSha256?: string;\n peerSha256?: string;\n /** Present only when a prior converged run left a cursor covering this path. */\n baseSha256?: string;\n /** Set only when `action` is `conflict`. */\n resolution?: ReconcileResolution;\n /** Original revision time when conflict resolution propagates a deletion. */\n deletionMtimeMs?: number;\n /**\n * Which side holds the retracted revision. Set only when `action` is\n * `suppress`: with different digests on each side and only one retracted,\n * the entry would otherwise be identical either way and transport could\n * delete the live copy instead of the retracted one.\n */\n suppressSide?: \"local\" | \"peer\" | \"both\";\n /**\n * Real per-side identities for a cross-path semantic agreement. Synthetic\n * rows omit the top-level side digests because `path` cannot name both files.\n */\n semanticAgreement?: ReconcileSemanticAgreement;\n /** Each side's current digest compared with its own prior semantic digest. */\n semanticChange?: ReconcileSemanticChange;\n}\n\nexport type ReconcileReason =\n | \"peer_only\"\n | \"local_only\"\n /** Cursor showed only that side moved since agreement; both still hold the path. */\n | \"peer_changed\"\n | \"local_changed\"\n | \"same_content\"\n | \"semantic_duplicate\"\n | \"both_modified\"\n | \"peer_deleted\"\n | \"local_deleted\"\n /** Peer deleted it while this side edited it — the offline-sync delete/modify pair. */\n | \"local_modified_peer_deleted\"\n | \"local_deleted_peer_modified\"\n | \"tombstoned\";\n\nexport interface ReconcileNamespaceReport {\n namespace: string;\n pull: number;\n push: number;\n identical: number;\n conflict: number;\n /** Local retractions the peer must be told about before the pair agrees. */\n suppress: number;\n /**\n * Conflicts still needing an operator: those the policy declined to settle,\n * plus supersede links whose direction could not be determined.\n */\n unresolved: number;\n}\n\nexport interface ReconcilePlan {\n entries: ReconcilePlanEntry[];\n byNamespace: ReconcileNamespaceReport[];\n /**\n * True when the two corpora already agree — every shared path matches and\n * neither side holds a path the other lacks.\n *\n * This is the idempotency contract from #2150: running reconciliation against\n * an already-converged peer must be a no-op, and a caller can skip the whole\n * transfer phase on this flag alone.\n */\n converged: boolean;\n}\n\n/** Minimal shape the planner needs; `OfflineSyncFileState` satisfies it. */\nexport type ReconcileFileState = Pick<OfflineSyncFileState, \"path\" | \"sha256\"> &\n Partial<Pick<OfflineSyncFileState, \"mtimeMs\" | \"bytes\">>;\n\nexport interface ReconcileNamespaceInput {\n namespace: string;\n local: Iterable<ReconcileFileState>;\n peer: Iterable<ReconcileFileState>;\n /**\n * File states agreed at the end of the last converged run with THIS peer.\n * Absent on a bootstrap merge, which is why a path missing from one side is\n * read as \"never seen\" rather than \"deleted\".\n */\n base?: Iterable<ReconcileFileState>;\n /**\n * Revision timestamps for paths deleted locally since the base cursor.\n * A deletion is a revision and must carry a comparable time for\n * `newest-wins`; absence alone has no timestamp.\n */\n localDeletionMtimeMs?: ReadonlyMap<string, number>;\n /** Revision timestamps for paths deleted by the peer since the base cursor. */\n peerDeletionMtimeMs?: ReadonlyMap<string, number>;\n /**\n * Digests of FILES this side has retracted, in the same form as\n * `ReconcileFileState.sha256` — a hash of the serialized file.\n *\n * Deliberately NOT `TombstoneEntry.contentHash`, which hashes the canonical\n * raw fact text and therefore never equals a file digest (§13: one content\n * form, everywhere). Mapping retracted fact hashes onto the file digests that\n * carry them is the caller's job, because only the caller can read its own\n * corpus; handing this the wrong form would silently plan `pull` and\n * resurrect every retracted fact, so the parameter name states the form.\n */\n tombstonedFileSha256?: Iterable<string>;\n /**\n * Digests the PEER has retracted, same form.\n *\n * Without this a bootstrap merge cannot tell \"the peer never had it\" from\n * \"the peer deliberately retracted it\": the peer census simply omits both,\n * so a file we still hold is planned `push` and the peer's retraction is\n * undone. Reconciliation is symmetric, so retraction has to be too.\n *\n * SCOPE: these sets decide what happens to files that still EXIST on one\n * side. Sharing the retraction records themselves is not modelled here -\n * tombstones are corpus files (`state/tombstones.jsonl`), so they reconcile\n * as ordinary paths through this same plan. A digest both censuses have\n * already dropped therefore produces no entry, by design: there is no file\n * left to act on, and synthesizing a path-less entry would hand transport\n * something it cannot apply.\n */\n peerTombstonedFileSha256?: Iterable<string>;\n}\n\nexport interface ReconcileOptions {\n conflictPolicy?: ConvergeConflictPolicy;\n}\n\n/**\n * A census the planner refuses to reason about.\n *\n * Dropping a malformed or contradictory record would let the planner return\n * `converged: true` for a corpus it could not actually read, and transport is\n * invited to skip everything on that flag — so bad input fails loudly instead\n * (§1/§39).\n */\nexport class ReconcilePlanInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ReconcilePlanInputError\";\n }\n}\n\n/** Shared with offline-sync's census validation (issue #2477): one acceptance set. */\n\n/**\n * Validate a census record's identity fields.\n *\n * The digest matters as much as the path: two records that both omit `sha256`\n * compare `undefined === undefined` and plan as `identical`, converging a\n * corpus the planner never actually read.\n */\nfunction assertCensusRecord(\n file: ReconcileFileState | undefined,\n side: string,\n namespace: string,\n): ReconcileFileState {\n const path = file?.path;\n if (typeof path !== \"string\" || path.length === 0) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} contains a record with no path`,\n );\n }\n // Same boundary offline-sync applies to this field: a peer census is\n // untrusted input, and an absolute or traversal path would otherwise become\n // a transfer instruction pointing outside the corpus root.\n try {\n validateArchiveRelativePath(path, `reconcile: ${side} census for namespace ${namespace}`);\n } catch (err) {\n throw new ReconcilePlanInputError(err instanceof Error ? err.message : String(err));\n }\n assertPortablePathSegments(path, side, namespace);\n return {\n ...file,\n path,\n sha256: assertDigest(file?.sha256, `${side} census for namespace ${namespace} entry ${path}`),\n ...(file?.mtimeMs === undefined\n ? {}\n : { mtimeMs: assertMtimeMs(file.mtimeMs, `${side} census for namespace ${namespace} entry ${path}`) }),\n };\n}\n\n/**\n * `newest-wins` decides which corpus keeps its history from this number, so a\n * NaN, an Infinity or a value past the Date range must not pick the winner.\n *\n * Fractional values ARE valid: `fs.stat()` reports sub-millisecond mtimes on\n * common filesystems and offline-sync forwards them unrounded, so this matches\n * its `assertOfflineSyncMtimeMs` — non-negative finite within Date range — and\n * deliberately does not require an integer.\n */\nfunction assertMtimeMs(value: unknown, context: string): number {\n if (!isCensusMtimeMs(value)) {\n throw new ReconcilePlanInputError(\n `reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${CENSUS_MAX_MTIME_MS}`,\n );\n }\n return value;\n}\n\n/**\n * Digests are canonicalized to lowercase, exactly as offline-sync's\n * `assertSha256` does. Keeping the caller's spelling would make two forms of\n * one digest compare unequal, so identical files would plan `both_modified`\n * and tombstone lookups would silently miss.\n */\nfunction assertDigest(value: unknown, context: string): string {\n if (!isSha256Hex(value)) {\n throw new ReconcilePlanInputError(\n `reconcile: ${context} must carry a 64-character sha256 hex digest`,\n );\n }\n return value.toLowerCase();\n}\n\nfunction isPlainObject(value: unknown): boolean {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction assertNamespace(namespace: unknown): string {\n if (typeof namespace !== \"string\" || namespace.length === 0) {\n throw new ReconcilePlanInputError(\"reconcile: every namespace input needs a non-empty namespace\");\n }\n // `team` and ` team ` are one namespace to the rest of core, so accepting\n // both here would slip two inputs past the duplicate check and let them plan\n // contradictory actions for the same path. Rejected rather than silently\n // rewritten, so the namespace on every entry is the caller's own string.\n if (LONE_SURROGATE.test(namespace)) {\n // `namespaceIdentityToken()` encodes through TextEncoder, so a lone\n // surrogate and a literal U+FFFD collapse to ONE storage identity while\n // comparing as two here - slipping both past the duplicate-namespace guard.\n throw new ReconcilePlanInputError(\n \"reconcile: namespace contains an unpaired surrogate; it would collide with another namespace on disk\",\n );\n }\n if (normalizeNamespaceIdentity(namespace) !== namespace) {\n throw new ReconcilePlanInputError(\n `reconcile: namespace ${JSON.stringify(namespace)} is not canonical; pass ${JSON.stringify(normalizeNamespaceIdentity(namespace))}`,\n );\n }\n return namespace;\n}\n\nfunction assertIterable(value: unknown, side: string, namespace: string): Iterable<ReconcileFileState> {\n if (value === null || typeof value !== \"object\" || typeof (value as Iterable<ReconcileFileState>)[Symbol.iterator] !== \"function\") {\n throw new ReconcilePlanInputError(`reconcile: ${side} census for namespace ${namespace} must be iterable`);\n }\n return value as Iterable<ReconcileFileState>;\n}\n\n/**\n * Two paths that a participant's filesystem resolves to ONE file must not draw\n * separate push/pull work, or application order decides which revision\n * survives. Remnic is explicitly multi-platform, so all three aliasing rules\n * are folded together and a collision is rejected rather than raced:\n * case (macOS, Windows), Unicode normalization (macOS stores decomposed and\n * compares canonically), and Win32 trailing dots/spaces, which the Windows API\n * strips before touching disk.\n */\n// eslint-disable-next-line no-control-regex -- control characters are exactly what this rejects\nconst WIN32_INVALID_CHARS = /[<>:\"|?*\\u0000-\\u001f]/;\n// Windows treats the superscripts as their digits in COM/LPT device names.\nconst LONE_SURROGATE = /[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/;\nconst WIN32_RESERVED_NAMES = /^(con|prn|aux|nul|(com|lpt)[1-9\\u00b9\\u00b2\\u00b3])$/i;\n\nfunction assertPortablePathSegments(path: string, side: string, namespace: string): void {\n for (const segment of path.split(\"/\")) {\n if (segment.length === 0) continue;\n if (segment.endsWith(\".\") || segment.endsWith(\" \")) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} path ${path} has a segment ending in a dot or space; ` +\n \"Windows strips those and would alias it onto another file\",\n );\n }\n // `:` opens an alternate data stream, the rest cannot be created at all,\n // and a reserved device name resolves to hardware. A plan containing any of\n // them cannot be applied on a Windows participant.\n if (LONE_SURROGATE.test(segment)) {\n // Node encodes an unpaired surrogate as U+FFFD, so this path and a\n // literal U+FFFD path are one file on disk while comparing as distinct.\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} path ${path} contains an unpaired surrogate`,\n );\n }\n if (WIN32_INVALID_CHARS.test(segment)) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} path ${path} contains a character Windows cannot store`,\n );\n }\n if (WIN32_RESERVED_NAMES.test(segment.split(\".\")[0] ?? \"\")) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} path ${path} uses a reserved Windows device name`,\n );\n }\n }\n}\n\nfunction assertNoPathAlias(\n seen: Map<string, string>,\n path: string,\n namespace: string,\n): void {\n // NFC first: macOS stores decomposed names and compares canonically, so\n // `é` (U+00E9) and `é` (e + U+0301) are ONE file there. Then a FULL caseless\n // fold - upper-then-lower, which collapses forms a bare toLowerCase() keeps\n // apart, such as final sigma `ς` against `σ`. Lowercasing alone would leave\n // those distinct and the planner would emit both a push and a pull.\n // `ß` upper-folds to `ss` while `ẞ` folds to `ß`, so one more pass equalizes\n // the pair that a single upper-then-lower still leaves apart.\n const folded = path.normalize(\"NFC\").toUpperCase().toLowerCase().replace(/\\u00df/g, \"ss\");\n const existing = seen.get(folded);\n if (existing !== undefined && existing !== path) {\n throw new ReconcilePlanInputError(\n `reconcile: namespace ${namespace} has aliasing paths (${existing}, ${path}); ` +\n \"they resolve to one file on a case-insensitive or Unicode-normalizing peer\",\n );\n }\n seen.set(folded, path);\n}\n\n/**\n * Duplicate paths are accepted only when they agree. Two digests for one path\n * make the plan depend on which record arrived last, which would break the\n * byte-stable ordering the convergence report relies on.\n */\nfunction rejectConflictingDuplicate(\n existing: { sha256: string; mtimeMs?: number } | undefined,\n incoming: { sha256: string; mtimeMs?: number },\n path: string,\n side: string,\n namespace: string,\n): boolean {\n if (!existing) return false;\n if (existing.sha256 !== incoming.sha256) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} lists ${path} twice with different digests`,\n );\n }\n // Same bytes but a different mtime is still ambiguous: `newest-wins` reads\n // that timestamp, so accepting the first arrival would let input order decide\n // the winner.\n if (existing.mtimeMs !== incoming.mtimeMs) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} lists ${path} twice with different mtimeMs`,\n );\n }\n return true;\n}\n\nfunction indexByPath(\n files: Iterable<ReconcileFileState>,\n side: string,\n namespace: string,\n): Map<string, ReconcileFileState> {\n const index = new Map<string, ReconcileFileState>();\n for (const raw of files) {\n const file = assertCensusRecord(raw, side, namespace);\n if (rejectConflictingDuplicate(index.get(file.path), file, file.path, side, namespace)) continue;\n index.set(file.path, file);\n }\n return index;\n}\n\n/**\n * A bare string satisfies `Iterable<string>`, and `new Set(\"abc…\")` would split\n * it into 64 one-character members — every membership test then misses and each\n * retracted file is planned as `pull` and resurrected. Reject the scalar and\n * canonicalize every member.\n */\nfunction parseTombstonedDigests(\n value: Iterable<string> | undefined,\n namespace: string,\n field = \"tombstonedFileSha256\",\n): Set<string> {\n if (value === undefined) return new Set();\n if (typeof value === \"string\") {\n throw new ReconcilePlanInputError(\n `reconcile: ${field} for namespace ${namespace} must be a collection of digests, not a single string`,\n );\n }\n if (value === null || typeof value !== \"object\" || typeof value[Symbol.iterator] !== \"function\") {\n // Otherwise the for...of below throws a raw TypeError and a caller handling\n // ReconcilePlanInputError cannot tell a bad request from a planner bug.\n throw new ReconcilePlanInputError(\n `reconcile: ${field} for namespace ${namespace} must be an iterable collection of digests`,\n );\n }\n const digests = new Set<string>();\n for (const entry of value) {\n digests.add(assertDigest(entry, `${field} for namespace ${namespace}`));\n }\n return digests;\n}\n\n/**\n * Validate a census while keeping ONLY path -> digest.\n *\n * The base cursor is read for digests alone, so indexing full records and\n * compacting afterwards would hold a corpus-sized record map and its copy at\n * once - the peak this avoids.\n */\nfunction indexDigestsByPath(\n files: Iterable<ReconcileFileState>,\n side: string,\n namespace: string,\n): Map<string, string> {\n const index = new Map<string, string>();\n for (const raw of files) {\n const file = assertCensusRecord(raw, side, namespace);\n const existing = index.get(file.path);\n // One map, not a parallel `seen`: the base's own mtimeMs is never a\n // decision input (only the local and peer timestamps order a conflict), so\n // a duplicate that agrees on the digest is unambiguous here regardless of\n // its timestamp, and the digest alone is enough to detect a real clash.\n if (existing !== undefined) {\n if (existing !== file.sha256) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} census for namespace ${namespace} lists ${file.path} twice with different digests`,\n );\n }\n continue;\n }\n index.set(file.path, file.sha256);\n }\n return index;\n}\n\nfunction assertConflictPolicy(value: ConvergeConflictPolicy | undefined): ConvergeConflictPolicy {\n if (value === undefined) return DEFAULT_CONVERGE_CONFLICT_POLICY;\n if (!CONVERGE_CONFLICT_POLICIES.includes(value)) {\n throw new ReconcilePlanInputError(\n `reconcile: unknown conflictPolicy ${JSON.stringify(value)}; expected one of ${CONVERGE_CONFLICT_POLICIES.join(\", \")}`,\n );\n }\n return value;\n}\n\n/**\n * Total ordering for plan entries (§12): namespace, then path. Both are unique\n * per entry, so equal keys are impossible and the comparator never has to\n * return 0 for distinct rows — the output is byte-identical across runs, which\n * is what makes a convergence report diffable. Cursor and manifest sort with\n * this same export; a local copy on either side is how ordering drifts (§2477).\n */\nexport function compareReconcilePlanEntries(a: ReconcilePlanEntry, b: ReconcilePlanEntry): number {\n if (a.namespace !== b.namespace) return a.namespace < b.namespace ? -1 : 1;\n if (a.path !== b.path) return a.path < b.path ? -1 : 1;\n return 0;\n}\n\n/**\n * Identity of a semantic-duplicate pair: the ordered (local, peer) path pair.\n * Cursor derivation and manifest collapse must key prior agreements with this\n * same export or a stored cursor stops matching the plan that wrote it.\n */\nexport function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nfunction resolveConflict(\n policy: ConvergeConflictPolicy,\n localMtimeMs: number | undefined,\n peerMtimeMs: number | undefined,\n missingTimestampResolution: ReconcileResolution,\n): ReconcileResolution {\n if (policy !== \"newest-wins\") return \"unresolved\";\n if (localMtimeMs === undefined || peerMtimeMs === undefined || localMtimeMs === peerMtimeMs) {\n return missingTimestampResolution;\n }\n return localMtimeMs > peerMtimeMs ? \"local-wins\" : \"peer-wins\";\n}\n\nfunction parseDeletionMtimes(\n value: ReadonlyMap<string, number> | undefined,\n side: string,\n namespace: string,\n): ReadonlyMap<string, number> {\n if (value === undefined) return new Map();\n if (!(value instanceof Map)) {\n throw new ReconcilePlanInputError(\n `reconcile: ${side} deletion mtimes for namespace ${namespace} must be a Map`,\n );\n }\n const parsed = new Map<string, number>();\n for (const [path, mtimeMs] of value) {\n try {\n validateArchiveRelativePath(path, `reconcile: ${side} deletion mtimes for namespace ${namespace}`);\n } catch (err) {\n throw new ReconcilePlanInputError(err instanceof Error ? err.message : String(err));\n }\n assertPortablePathSegments(path, side, namespace);\n parsed.set(path, assertMtimeMs(mtimeMs, `${side} deletion for namespace ${namespace} entry ${path}`));\n }\n return parsed;\n}\n\n/**\n * Plan one namespace. Exposed for callers that stream namespaces one at a time.\n *\n * Residency is one peer index + a set of local paths + the entry list. The\n * local census is consumed as a stream and never materialized, so a 100k-file\n * corpus does not hold two full censuses at once — but this is NOT constant\n * memory, and a caller holding its own census arrays adds to that.\n */\nexport function planNamespaceReconciliation(\n input: ReconcileNamespaceInput,\n options: ReconcileOptions = {},\n): ReconcilePlanEntry[] {\n if (!isPlainObject(input)) {\n throw new ReconcilePlanInputError(\"reconcile: namespace input must be a plain object\");\n }\n if (!isPlainObject(options)) {\n // A Date, Map or RegExp passes a bare typeof check, exposes no\n // `conflictPolicy`, and would silently take the default policy - hiding a\n // malformed request behind `manual`.\n throw new ReconcilePlanInputError(\"reconcile: options must be a plain object\");\n }\n const namespace = assertNamespace(input.namespace);\n const policy = assertConflictPolicy(options.conflictPolicy);\n const localCensus = assertIterable(input.local, \"local\", namespace);\n const caseFold = new Map<string, string>();\n // Index the peer census only, then stream the local one against it, removing\n // each match as it is consumed. Peak residency is one index plus the entry\n // list rather than two full censuses (round 2, codex P2).\n const peer = indexByPath(assertIterable(input.peer, \"peer\", namespace), \"peer\", namespace);\n // Only `base.sha256` is ever read, so the base is compacted to path -> digest\n // instead of a second full record index (round 7, codex P2).\n // Only an ABSENT base means bootstrap. A null/invalid cursor silently read as\n // \"no prior agreement\" would turn a peer-side deletion into a push and\n // resurrect it.\n const base = input.base === undefined\n ? null\n : indexDigestsByPath(assertIterable(input.base, \"base\", namespace), \"base\", namespace);\n const tombstoned = parseTombstonedDigests(input.tombstonedFileSha256, namespace);\n const peerTombstoned = parseTombstonedDigests(\n input.peerTombstonedFileSha256,\n namespace,\n \"peerTombstonedFileSha256\",\n );\n const localDeletionMtimeMs = parseDeletionMtimes(input.localDeletionMtimeMs, \"local\", namespace);\n const peerDeletionMtimeMs = parseDeletionMtimes(input.peerDeletionMtimeMs, \"peer\", namespace);\n const entries: ReconcilePlanEntry[] = [];\n\n // Path -> digest only, never the file objects: enough to make the stream\n // idempotent and to catch contradictory duplicates without materializing the\n // second census.\n // Base paths participate in the collision check: a base `Facts/A.md` against\n // a local `facts/a.md` is the same delete/change ambiguity on a\n // case-insensitive participant.\n if (base) for (const basePath of base.keys()) assertNoPathAlias(caseFold, basePath, namespace);\n // Path -> decision-relevant fields only, never the whole record: enough to\n // make the stream idempotent and to catch contradictory duplicates without\n // materializing the second census.\n const seenLocal = new Map<string, { sha256: string; mtimeMs?: number }>();\n for (const rawLocal of localCensus) {\n const localFile = assertCensusRecord(rawLocal, \"local\", namespace);\n const path = localFile.path;\n assertNoPathAlias(caseFold, path, namespace);\n const seen = seenLocal.get(path);\n if (seen !== undefined) {\n // Same rule the indexed censuses get: mtimeMs is decision-relevant under\n // `newest-wins`, so a duplicate that disagrees on it is ambiguous too.\n if (rejectConflictingDuplicate(seen, localFile, path, \"local\", namespace)) continue;\n }\n seenLocal.set(path, { sha256: localFile.sha256, mtimeMs: localFile.mtimeMs });\n const peerFile = peer.get(path);\n // Consumed: whatever remains in the index afterwards is peer-only.\n peer.delete(path);\n const baseSha256 = base?.get(path);\n // Retraction outranks every other decision for this path, on EITHER side.\n // Checked before hash comparison and conflict resolution: a retracted peer\n // revision reaching the conflict ladder can win under `newest-wins`, and a\n // retracted LOCAL revision would otherwise be pushed - which is also how a\n // suppression undoes itself on the next run, since the local copy survives\n // the peer-side delete (round 4).\n // Either side's retraction removes the copy that carries the digest, so a\n // peer retraction of something we still hold suppresses OUR copy rather\n // than pushing it back.\n const localRetracted = tombstoned.has(localFile.sha256) || peerTombstoned.has(localFile.sha256);\n const peerRetracted =\n peerFile !== undefined && (tombstoned.has(peerFile.sha256) || peerTombstoned.has(peerFile.sha256));\n if (localRetracted || peerRetracted) {\n entries.push({\n path,\n namespace,\n action: \"suppress\",\n reason: \"tombstoned\",\n localSha256: localFile.sha256,\n ...(peerFile ? { peerSha256: peerFile.sha256 } : {}),\n ...(baseSha256 === undefined ? {} : { baseSha256 }),\n suppressSide: localRetracted && peerRetracted ? \"both\" : localRetracted ? \"local\" : \"peer\",\n });\n continue;\n }\n if (!peerFile) {\n // Base PRESENCE is what proves a deletion, not equality with whatever the\n // surviving side now holds. If we also edited since the base, this is\n // delete-versus-modify: still a conflict, and pushing it would resurrect\n // a deliberate deletion. Without a base the peer simply never saw the\n // path, and a bootstrap merge must push — both sides hold unique data.\n if (baseSha256 !== undefined) {\n const peerDeletionMtime = peerDeletionMtimeMs.get(path);\n const resolution = baseSha256 === localFile.sha256\n ? \"unresolved\"\n : resolveConflict(\n policy,\n localFile.mtimeMs,\n peerDeletionMtime,\n \"unresolved\",\n );\n entries.push({\n path,\n namespace,\n action: \"conflict\",\n reason: baseSha256 === localFile.sha256 ? \"peer_deleted\" : \"local_modified_peer_deleted\",\n localSha256: localFile.sha256,\n baseSha256,\n resolution,\n ...(resolution === \"peer-wins\" && peerDeletionMtime !== undefined\n ? { deletionMtimeMs: peerDeletionMtime }\n : {}),\n });\n continue;\n }\n entries.push({\n path,\n namespace,\n action: \"push\",\n reason: \"local_only\",\n localSha256: localFile.sha256,\n });\n continue;\n }\n if (peerFile.sha256 === localFile.sha256) {\n entries.push({\n path,\n namespace,\n action: \"identical\",\n reason: \"same_content\",\n localSha256: localFile.sha256,\n peerSha256: peerFile.sha256,\n ...(baseSha256 === undefined ? {} : { baseSha256 }),\n });\n continue;\n }\n // A base that matches one side turns a \"conflict\" into an ordinary\n // one-sided change: that side is the only one that moved since agreement.\n if (baseSha256 !== undefined && baseSha256 === localFile.sha256) {\n entries.push({\n path,\n namespace,\n action: \"pull\",\n reason: \"peer_changed\",\n localSha256: localFile.sha256,\n peerSha256: peerFile.sha256,\n baseSha256,\n });\n continue;\n }\n if (baseSha256 !== undefined && baseSha256 === peerFile.sha256) {\n entries.push({\n path,\n namespace,\n action: \"push\",\n reason: \"local_changed\",\n localSha256: localFile.sha256,\n peerSha256: peerFile.sha256,\n baseSha256,\n });\n continue;\n }\n const resolution = resolveConflict(\n policy,\n localFile.mtimeMs,\n peerFile.mtimeMs,\n \"supersede-link\",\n );\n entries.push({\n path,\n namespace,\n action: \"conflict\",\n reason: \"both_modified\",\n localSha256: localFile.sha256,\n peerSha256: peerFile.sha256,\n ...(baseSha256 === undefined ? {} : { baseSha256 }),\n resolution,\n });\n }\n\n for (const [path, peerFile] of peer) {\n assertNoPathAlias(caseFold, path, namespace);\n const baseSha256 = base?.get(path);\n if (tombstoned.has(peerFile.sha256) || peerTombstoned.has(peerFile.sha256)) {\n // Retracted here on purpose, and the peer still serves it. Pulling it\n // back would undo the retraction; calling it `identical` would be worse,\n // because a converged plan lets transport skip everything and the peer\n // keeps serving the retracted fact forever. It is work: propagate the\n // tombstone.\n entries.push({\n path,\n namespace,\n action: \"suppress\",\n reason: \"tombstoned\",\n peerSha256: peerFile.sha256,\n ...(baseSha256 === undefined ? {} : { baseSha256 }),\n suppressSide: \"peer\",\n });\n continue;\n }\n if (baseSha256 !== undefined) {\n const localDeletionMtime = localDeletionMtimeMs.get(path);\n const resolution = baseSha256 === peerFile.sha256\n ? \"unresolved\"\n : resolveConflict(\n policy,\n localDeletionMtime,\n peerFile.mtimeMs,\n \"unresolved\",\n );\n entries.push({\n path,\n namespace,\n action: \"conflict\",\n reason: baseSha256 === peerFile.sha256 ? \"local_deleted\" : \"local_deleted_peer_modified\",\n peerSha256: peerFile.sha256,\n baseSha256,\n resolution,\n ...(resolution === \"local-wins\" && localDeletionMtime !== undefined\n ? { deletionMtimeMs: localDeletionMtime }\n : {}),\n });\n continue;\n }\n entries.push({\n path,\n namespace,\n action: \"pull\",\n reason: \"peer_only\",\n peerSha256: peerFile.sha256,\n });\n }\n\n return entries.sort(compareReconcilePlanEntries);\n}\n\n/** Aggregate entries into the per-namespace convergence report (#2150). */\nexport function summarizeReconcilePlan(entries: readonly ReconcilePlanEntry[]): ReconcileNamespaceReport[] {\n const byNamespace = new Map<string, ReconcileNamespaceReport>();\n for (const entry of entries) {\n let report = byNamespace.get(entry.namespace);\n if (!report) {\n report = { namespace: entry.namespace, pull: 0, push: 0, identical: 0, conflict: 0, suppress: 0, unresolved: 0 };\n byNamespace.set(entry.namespace, report);\n }\n report[entry.action] += 1;\n const needsOperator =\n entry.resolution === \"unresolved\"\n || entry.resolution === \"supersede-link\";\n if (entry.action === \"conflict\" && needsOperator) report.unresolved += 1;\n }\n return [...byNamespace.values()].sort((a, b) => (a.namespace === b.namespace ? 0 : a.namespace < b.namespace ? -1 : 1));\n}\n\n/**\n * Plan a full reconciliation across namespaces.\n *\n * `converged` is an affirmative claim that nothing needs to move, so it is\n * derived from the entries rather than tracked alongside them: any action other\n * than `identical` disproves it.\n */\nexport function planReconciliation(\n namespaces: readonly ReconcileNamespaceInput[],\n options: ReconcileOptions = {},\n): ReconcilePlan {\n if (!Array.isArray(namespaces)) {\n throw new ReconcilePlanInputError(\"reconcile: planReconciliation expects an array of namespace inputs\");\n }\n // Validated here too: with an empty array the per-namespace path never runs,\n // and a malformed options envelope would return `converged` instead of\n // raising - the same call failing or succeeding based on list length.\n if (!isPlainObject(options)) {\n throw new ReconcilePlanInputError(\"reconcile: options must be a plain object\");\n }\n assertConflictPolicy(options.conflictPolicy);\n const entries: ReconcilePlanEntry[] = [];\n // Two inputs for one namespace are planned independently, so the same\n // (namespace, path) can draw contradictory actions - and because those\n // entries also sort equal, batch order would decide which revision survives.\n const seenNamespaces = new Set<string>();\n for (const namespace of namespaces) {\n const name = assertNamespace(namespace?.namespace);\n if (seenNamespaces.has(name)) {\n throw new ReconcilePlanInputError(\n `reconcile: namespace ${name} appears twice; merge its censuses before planning`,\n );\n }\n seenNamespaces.add(name);\n entries.push(...planNamespaceReconciliation(namespace, options));\n }\n entries.sort(compareReconcilePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgMO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACjD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAWA,SAAS,mBACP,MACA,MACA,WACoB;AACpB,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG;AACjD,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,yBAAyB,SAAS;AAAA,IACtD;AAAA,EACF;AAIA,MAAI;AACF,gCAA4B,MAAM,cAAc,IAAI,yBAAyB,SAAS,EAAE;AAAA,EAC1F,SAAS,KAAK;AACZ,UAAM,IAAI,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,EACpF;AACA,6BAA2B,MAAM,MAAM,SAAS;AAChD,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,QAAQ,aAAa,MAAM,QAAQ,GAAG,IAAI,yBAAyB,SAAS,UAAU,IAAI,EAAE;AAAA,IAC5F,GAAI,MAAM,YAAY,SAClB,CAAC,IACD,EAAE,SAAS,cAAc,KAAK,SAAS,GAAG,IAAI,yBAAyB,SAAS,UAAU,IAAI,EAAE,EAAE;AAAA,EACxG;AACF;AAWA,SAAS,cAAc,OAAgB,SAAyB;AAC9D,MAAI,CAAC,gBAAgB,KAAK,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR,cAAc,OAAO,uEAAuE,mBAAmB;AAAA,IACjH;AAAA,EACF;AACA,SAAO;AACT;AAQA,SAAS,aAAa,OAAgB,SAAyB;AAC7D,MAAI,CAAC,YAAY,KAAK,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,cAAc,OAAO;AAAA,IACvB;AAAA,EACF;AACA,SAAO,MAAM,YAAY;AAC3B;AAEA,SAAS,cAAc,OAAyB;AAC9C,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,QAAM,QAAiB,OAAO,eAAe,KAAK;AAClD,SAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEA,SAAS,gBAAgB,WAA4B;AACnD,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,GAAG;AAC3D,UAAM,IAAI,wBAAwB,8DAA8D;AAAA,EAClG;AAKA,MAAI,eAAe,KAAK,SAAS,GAAG;AAIlC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,2BAA2B,SAAS,MAAM,WAAW;AACvD,UAAM,IAAI;AAAA,MACR,wBAAwB,KAAK,UAAU,SAAS,CAAC,2BAA2B,KAAK,UAAU,2BAA2B,SAAS,CAAC,CAAC;AAAA,IACnI;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAgB,MAAc,WAAiD;AACrG,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAQ,MAAuC,OAAO,QAAQ,MAAM,YAAY;AACjI,UAAM,IAAI,wBAAwB,cAAc,IAAI,yBAAyB,SAAS,mBAAmB;AAAA,EAC3G;AACA,SAAO;AACT;AAYA,IAAM,sBAAsB;AAE5B,IAAM,iBAAiB;AACvB,IAAM,uBAAuB;AAE7B,SAAS,2BAA2B,MAAc,MAAc,WAAyB;AACvF,aAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AACrC,QAAI,QAAQ,WAAW,EAAG;AAC1B,QAAI,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAG;AAClD,YAAM,IAAI;AAAA,QACR,cAAc,IAAI,yBAAyB,SAAS,SAAS,IAAI;AAAA,MAEnE;AAAA,IACF;AAIA,QAAI,eAAe,KAAK,OAAO,GAAG;AAGhC,YAAM,IAAI;AAAA,QACR,cAAc,IAAI,yBAAyB,SAAS,SAAS,IAAI;AAAA,MACnE;AAAA,IACF;AACA,QAAI,oBAAoB,KAAK,OAAO,GAAG;AACrC,YAAM,IAAI;AAAA,QACR,cAAc,IAAI,yBAAyB,SAAS,SAAS,IAAI;AAAA,MACnE;AAAA,IACF;AACA,QAAI,qBAAqB,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG;AAC1D,YAAM,IAAI;AAAA,QACR,cAAc,IAAI,yBAAyB,SAAS,SAAS,IAAI;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,kBACP,MACA,MACA,WACM;AAQN,QAAM,SAAS,KAAK,UAAU,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,WAAW,IAAI;AACxF,QAAM,WAAW,KAAK,IAAI,MAAM;AAChC,MAAI,aAAa,UAAa,aAAa,MAAM;AAC/C,UAAM,IAAI;AAAA,MACR,wBAAwB,SAAS,wBAAwB,QAAQ,KAAK,IAAI;AAAA,IAE5E;AAAA,EACF;AACA,OAAK,IAAI,QAAQ,IAAI;AACvB;AAOA,SAAS,2BACP,UACA,UACA,MACA,MACA,WACS;AACT,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,SAAS,WAAW,SAAS,QAAQ;AACvC,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,yBAAyB,SAAS,UAAU,IAAI;AAAA,IACpE;AAAA,EACF;AAIA,MAAI,SAAS,YAAY,SAAS,SAAS;AACzC,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,yBAAyB,SAAS,UAAU,IAAI;AAAA,IACpE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YACP,OACA,MACA,WACiC;AACjC,QAAM,QAAQ,oBAAI,IAAgC;AAClD,aAAW,OAAO,OAAO;AACvB,UAAM,OAAO,mBAAmB,KAAK,MAAM,SAAS;AACpD,QAAI,2BAA2B,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,MAAM,SAAS,EAAG;AACxF,UAAM,IAAI,KAAK,MAAM,IAAI;AAAA,EAC3B;AACA,SAAO;AACT;AAQA,SAAS,uBACP,OACA,WACA,QAAQ,wBACK;AACb,MAAI,UAAU,OAAW,QAAO,oBAAI,IAAI;AACxC,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI;AAAA,MACR,cAAc,KAAK,kBAAkB,SAAS;AAAA,IAChD;AAAA,EACF;AACA,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,MAAM,OAAO,QAAQ,MAAM,YAAY;AAG/F,UAAM,IAAI;AAAA,MACR,cAAc,KAAK,kBAAkB,SAAS;AAAA,IAChD;AAAA,EACF;AACA,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,OAAO;AACzB,YAAQ,IAAI,aAAa,OAAO,GAAG,KAAK,kBAAkB,SAAS,EAAE,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AASA,SAAS,mBACP,OACA,MACA,WACqB;AACrB,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,OAAO,OAAO;AACvB,UAAM,OAAO,mBAAmB,KAAK,MAAM,SAAS;AACpD,UAAM,WAAW,MAAM,IAAI,KAAK,IAAI;AAKpC,QAAI,aAAa,QAAW;AAC1B,UAAI,aAAa,KAAK,QAAQ;AAC5B,cAAM,IAAI;AAAA,UACR,cAAc,IAAI,yBAAyB,SAAS,UAAU,KAAK,IAAI;AAAA,QACzE;AAAA,MACF;AACA;AAAA,IACF;AACA,UAAM,IAAI,KAAK,MAAM,KAAK,MAAM;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAmE;AAC/F,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,CAAC,2BAA2B,SAAS,KAAK,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR,qCAAqC,KAAK,UAAU,KAAK,CAAC,qBAAqB,2BAA2B,KAAK,IAAI,CAAC;AAAA,IACtH;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,4BAA4B,GAAuB,GAA+B;AAChG,MAAI,EAAE,cAAc,EAAE,UAAW,QAAO,EAAE,YAAY,EAAE,YAAY,KAAK;AACzE,MAAI,EAAE,SAAS,EAAE,KAAM,QAAO,EAAE,OAAO,EAAE,OAAO,KAAK;AACrD,SAAO;AACT;AAOO,SAAS,qBAAqB,WAA+C;AAClF,SAAO,GAAG,UAAU,MAAM,IAAI,KAAK,UAAU,KAAK,IAAI;AACxD;AAEA,SAAS,gBACP,QACA,cACA,aACA,4BACqB;AACrB,MAAI,WAAW,cAAe,QAAO;AACrC,MAAI,iBAAiB,UAAa,gBAAgB,UAAa,iBAAiB,aAAa;AAC3F,WAAO;AAAA,EACT;AACA,SAAO,eAAe,cAAc,eAAe;AACrD;AAEA,SAAS,oBACP,OACA,MACA,WAC6B;AAC7B,MAAI,UAAU,OAAW,QAAO,oBAAI,IAAI;AACxC,MAAI,EAAE,iBAAiB,MAAM;AAC3B,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,kCAAkC,SAAS;AAAA,IAC/D;AAAA,EACF;AACA,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO;AACnC,QAAI;AACF,kCAA4B,MAAM,cAAc,IAAI,kCAAkC,SAAS,EAAE;AAAA,IACnG,SAAS,KAAK;AACZ,YAAM,IAAI,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACpF;AACA,+BAA2B,MAAM,MAAM,SAAS;AAChD,WAAO,IAAI,MAAM,cAAc,SAAS,GAAG,IAAI,2BAA2B,SAAS,UAAU,IAAI,EAAE,CAAC;AAAA,EACtG;AACA,SAAO;AACT;AAUO,SAAS,4BACd,OACA,UAA4B,CAAC,GACP;AACtB,MAAI,CAAC,cAAc,KAAK,GAAG;AACzB,UAAM,IAAI,wBAAwB,mDAAmD;AAAA,EACvF;AACA,MAAI,CAAC,cAAc,OAAO,GAAG;AAI3B,UAAM,IAAI,wBAAwB,2CAA2C;AAAA,EAC/E;AACA,QAAM,YAAY,gBAAgB,MAAM,SAAS;AACjD,QAAM,SAAS,qBAAqB,QAAQ,cAAc;AAC1D,QAAM,cAAc,eAAe,MAAM,OAAO,SAAS,SAAS;AAClE,QAAM,WAAW,oBAAI,IAAoB;AAIzC,QAAM,OAAO,YAAY,eAAe,MAAM,MAAM,QAAQ,SAAS,GAAG,QAAQ,SAAS;AAMzF,QAAM,OAAO,MAAM,SAAS,SACxB,OACA,mBAAmB,eAAe,MAAM,MAAM,QAAQ,SAAS,GAAG,QAAQ,SAAS;AACvF,QAAM,aAAa,uBAAuB,MAAM,sBAAsB,SAAS;AAC/E,QAAM,iBAAiB;AAAA,IACrB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACA,QAAM,uBAAuB,oBAAoB,MAAM,sBAAsB,SAAS,SAAS;AAC/F,QAAM,sBAAsB,oBAAoB,MAAM,qBAAqB,QAAQ,SAAS;AAC5F,QAAM,UAAgC,CAAC;AAQvC,MAAI,KAAM,YAAW,YAAY,KAAK,KAAK,EAAG,mBAAkB,UAAU,UAAU,SAAS;AAI7F,QAAM,YAAY,oBAAI,IAAkD;AACxE,aAAW,YAAY,aAAa;AAClC,UAAM,YAAY,mBAAmB,UAAU,SAAS,SAAS;AACjE,UAAM,OAAO,UAAU;AACvB,sBAAkB,UAAU,MAAM,SAAS;AAC3C,UAAM,OAAO,UAAU,IAAI,IAAI;AAC/B,QAAI,SAAS,QAAW;AAGtB,UAAI,2BAA2B,MAAM,WAAW,MAAM,SAAS,SAAS,EAAG;AAAA,IAC7E;AACA,cAAU,IAAI,MAAM,EAAE,QAAQ,UAAU,QAAQ,SAAS,UAAU,QAAQ,CAAC;AAC5E,UAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAK,OAAO,IAAI;AAChB,UAAM,aAAa,MAAM,IAAI,IAAI;AAUjC,UAAM,iBAAiB,WAAW,IAAI,UAAU,MAAM,KAAK,eAAe,IAAI,UAAU,MAAM;AAC9F,UAAM,gBACJ,aAAa,WAAc,WAAW,IAAI,SAAS,MAAM,KAAK,eAAe,IAAI,SAAS,MAAM;AAClG,QAAI,kBAAkB,eAAe;AACnC,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,UAAU;AAAA,QACvB,GAAI,WAAW,EAAE,YAAY,SAAS,OAAO,IAAI,CAAC;AAAA,QAClD,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,QACjD,cAAc,kBAAkB,gBAAgB,SAAS,iBAAiB,UAAU;AAAA,MACtF,CAAC;AACD;AAAA,IACF;AACA,QAAI,CAAC,UAAU;AAMb,UAAI,eAAe,QAAW;AAC5B,cAAM,oBAAoB,oBAAoB,IAAI,IAAI;AACtD,cAAMA,cAAa,eAAe,UAAU,SACxC,eACA;AAAA,UACE;AAAA,UACA,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QACF;AACJ,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ,eAAe,UAAU,SAAS,iBAAiB;AAAA,UAC3D,aAAa,UAAU;AAAA,UACvB;AAAA,UACA,YAAAA;AAAA,UACA,GAAIA,gBAAe,eAAe,sBAAsB,SACpD,EAAE,iBAAiB,kBAAkB,IACrC,CAAC;AAAA,QACP,CAAC;AACD;AAAA,MACF;AACA,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,UAAU;AAAA,MACzB,CAAC;AACD;AAAA,IACF;AACA,QAAI,SAAS,WAAW,UAAU,QAAQ;AACxC,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,UAAU;AAAA,QACvB,YAAY,SAAS;AAAA,QACrB,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,MACnD,CAAC;AACD;AAAA,IACF;AAGA,QAAI,eAAe,UAAa,eAAe,UAAU,QAAQ;AAC/D,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,UAAU;AAAA,QACvB,YAAY,SAAS;AAAA,QACrB;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,QAAI,eAAe,UAAa,eAAe,SAAS,QAAQ;AAC9D,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,UAAU;AAAA,QACvB,YAAY,SAAS;AAAA,QACrB;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT;AAAA,IACF;AACA,YAAQ,KAAK;AAAA,MACX;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,UAAU;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,aAAW,CAAC,MAAM,QAAQ,KAAK,MAAM;AACnC,sBAAkB,UAAU,MAAM,SAAS;AAC3C,UAAM,aAAa,MAAM,IAAI,IAAI;AACjC,QAAI,WAAW,IAAI,SAAS,MAAM,KAAK,eAAe,IAAI,SAAS,MAAM,GAAG;AAM1E,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,YAAY,SAAS;AAAA,QACrB,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,QACjD,cAAc;AAAA,MAChB,CAAC;AACD;AAAA,IACF;AACA,QAAI,eAAe,QAAW;AAC5B,YAAM,qBAAqB,qBAAqB,IAAI,IAAI;AACxD,YAAM,aAAa,eAAe,SAAS,SACvC,eACA;AAAA,QACE;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT;AAAA,MACF;AACJ,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ,eAAe,SAAS,SAAS,kBAAkB;AAAA,QAC3D,YAAY,SAAS;AAAA,QACrB;AAAA,QACA;AAAA,QACA,GAAI,eAAe,gBAAgB,uBAAuB,SACtD,EAAE,iBAAiB,mBAAmB,IACtC,CAAC;AAAA,MACP,CAAC;AACD;AAAA,IACF;AACA,YAAQ,KAAK;AAAA,MACX;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY,SAAS;AAAA,IACvB,CAAC;AAAA,EACH;AAEA,SAAO,QAAQ,KAAK,2BAA2B;AACjD;AAGO,SAAS,uBAAuB,SAAoE;AACzG,QAAM,cAAc,oBAAI,IAAsC;AAC9D,aAAW,SAAS,SAAS;AAC3B,QAAI,SAAS,YAAY,IAAI,MAAM,SAAS;AAC5C,QAAI,CAAC,QAAQ;AACX,eAAS,EAAE,WAAW,MAAM,WAAW,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,EAAE;AAC/G,kBAAY,IAAI,MAAM,WAAW,MAAM;AAAA,IACzC;AACA,WAAO,MAAM,MAAM,KAAK;AACxB,UAAM,gBACJ,MAAM,eAAe,gBAClB,MAAM,eAAe;AAC1B,QAAI,MAAM,WAAW,cAAc,cAAe,QAAO,cAAc;AAAA,EACzE;AACA,SAAO,CAAC,GAAG,YAAY,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,cAAc,EAAE,YAAY,IAAI,EAAE,YAAY,EAAE,YAAY,KAAK,CAAE;AACxH;AASO,SAAS,mBACd,YACA,UAA4B,CAAC,GACd;AACf,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,IAAI,wBAAwB,oEAAoE;AAAA,EACxG;AAIA,MAAI,CAAC,cAAc,OAAO,GAAG;AAC3B,UAAM,IAAI,wBAAwB,2CAA2C;AAAA,EAC/E;AACA,uBAAqB,QAAQ,cAAc;AAC3C,QAAM,UAAgC,CAAC;AAIvC,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,aAAa,YAAY;AAClC,UAAM,OAAO,gBAAgB,WAAW,SAAS;AACjD,QAAI,eAAe,IAAI,IAAI,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,wBAAwB,IAAI;AAAA,MAC9B;AAAA,IACF;AACA,mBAAe,IAAI,IAAI;AACvB,YAAQ,KAAK,GAAG,4BAA4B,WAAW,OAAO,CAAC;AAAA,EACjE;AACA,UAAQ,KAAK,2BAA2B;AACxC,SAAO;AAAA,IACL;AAAA,IACA,aAAa,uBAAuB,OAAO;AAAA,IAC3C,WAAW,QAAQ,MAAM,CAAC,UAAU,MAAM,WAAW,WAAW;AAAA,EAClE;AACF;","names":["resolution"]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ coerceIncludedMemories
3
+ } from "./chunk-2YA5N2BY.js";
1
4
  import {
2
5
  writeFileAtomically
3
6
  } from "./chunk-AW4QUFQF.js";
@@ -19,6 +22,9 @@ import {
19
22
  import { appendFile, mkdir, readFile, rename, rm, stat, unlink, writeFile } from "fs/promises";
20
23
  import path from "path";
21
24
  import { createHash, randomUUID } from "crypto";
25
+ function isUnsafeStateKey(key) {
26
+ return key === "__proto__" || key === "constructor" || key === "prototype";
27
+ }
22
28
  function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
23
29
  const limit = Math.max(1, Math.floor(maxEntries));
24
30
  if (!Array.isArray(entries)) return [];
@@ -45,27 +51,42 @@ function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
45
51
  return out;
46
52
  }).filter((item) => item.path.length > 0 && item.namespace.length > 0).slice(0, limit);
47
53
  }
54
+ function cloneTierExplain(tierExplain) {
55
+ if (!tierExplain) return void 0;
56
+ return structuredClone(tierExplain);
57
+ }
48
58
  function snapshotMatchesExpectedIdentity(current, expected) {
49
59
  if (!expected) return true;
50
60
  if (typeof expected.writeNonce === "string" && expected.writeNonce.length > 0) {
51
61
  return current.writeNonce === expected.writeNonce;
52
62
  }
53
63
  const hasExpectedTraceId = typeof expected.traceId === "string" && expected.traceId.length > 0;
54
- if (hasExpectedTraceId) {
55
- return current.traceId === expected.traceId;
56
- }
57
- if (expected.recordedAt !== void 0) {
64
+ if (hasExpectedTraceId && current.traceId !== expected.traceId) return false;
65
+ if (typeof expected.recordedAt === "string" && expected.recordedAt.length > 0) {
58
66
  return current.recordedAt === expected.recordedAt;
59
67
  }
60
68
  return true;
61
69
  }
62
- function cloneTierExplain(tierExplain) {
63
- if (!tierExplain) return void 0;
64
- return structuredClone(tierExplain);
70
+ function hydrateLastRecallSnapshot(snapshot) {
71
+ const includedMemories = coerceIncludedMemories(snapshot);
72
+ const budgets = snapshot.budgetsApplied ? {
73
+ ...snapshot.budgetsApplied,
74
+ includedMemoryIds: includedMemories.map((memory) => memory.id),
75
+ includedMemoryPaths: includedMemories.map((memory) => memory.path),
76
+ includedMemoryNamespaces: includedMemories.map((memory) => memory.namespace)
77
+ } : void 0;
78
+ return {
79
+ ...snapshot,
80
+ includedMemories,
81
+ memoryIds: includedMemories.map((memory) => memory.id),
82
+ resultPaths: includedMemories.map((memory) => memory.path),
83
+ resultNamespaces: includedMemories.map((memory) => memory.namespace),
84
+ ...budgets ? { budgetsApplied: budgets } : {}
85
+ };
65
86
  }
66
87
  function cloneLastRecallSnapshot(snapshot) {
67
88
  if (!snapshot) return null;
68
- return structuredClone(snapshot);
89
+ return structuredClone(hydrateLastRecallSnapshot(snapshot));
69
90
  }
70
91
  var DEFAULT_TIER_MIGRATION_STATUS = {
71
92
  updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
@@ -109,12 +130,27 @@ var LastRecallStore = class {
109
130
  try {
110
131
  const raw = await readFile(this.statePath, "utf-8");
111
132
  const parsed = JSON.parse(raw);
112
- if (parsed && typeof parsed === "object") this.state = parsed;
133
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
134
+ this.state = {};
135
+ return;
136
+ }
137
+ const next = {};
138
+ for (const [sessionKey, snapshot] of Object.entries(parsed)) {
139
+ if (sessionKey === "__proto__" || sessionKey === "constructor" || sessionKey === "prototype") {
140
+ continue;
141
+ }
142
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
143
+ continue;
144
+ }
145
+ next[sessionKey] = hydrateLastRecallSnapshot(snapshot);
146
+ }
147
+ this.state = next;
113
148
  } catch {
114
149
  this.state = {};
115
150
  }
116
151
  }
117
152
  get(sessionKey) {
153
+ if (isUnsafeStateKey(sessionKey)) return null;
118
154
  return cloneLastRecallSnapshot(this.state[sessionKey] ?? null);
119
155
  }
120
156
  getMostRecent() {
@@ -132,15 +168,21 @@ var LastRecallStore = class {
132
168
  * Does not store raw query text; uses a stable hash for correlation.
133
169
  */
134
170
  async record(opts) {
171
+ if (isUnsafeStateKey(opts.sessionKey)) {
172
+ log.debug("last recall record skipped: unsafe session key");
173
+ return;
174
+ }
135
175
  const now = (/* @__PURE__ */ new Date()).toISOString();
136
176
  const queryHash = createHash("sha256").update(opts.query).digest("hex");
177
+ const includedMemories = coerceIncludedMemories(opts);
137
178
  const liveSnapshot = {
138
179
  sessionKey: opts.sessionKey,
139
180
  recordedAt: now,
140
181
  queryHash,
141
182
  writeNonce: randomUUID(),
142
183
  queryLen: opts.query.length,
143
- memoryIds: opts.memoryIds,
184
+ includedMemories,
185
+ memoryIds: includedMemories.map((memory) => memory.id),
144
186
  namespace: opts.namespace,
145
187
  recallNamespaces: opts.recallNamespaces,
146
188
  traceId: opts.traceId,
@@ -151,8 +193,6 @@ var LastRecallStore = class {
151
193
  sourcesUsed: opts.sourcesUsed,
152
194
  budgetsApplied: opts.budgetsApplied,
153
195
  latencyMs: opts.latencyMs,
154
- resultPaths: opts.resultPaths,
155
- resultNamespaces: opts.resultNamespaces,
156
196
  policyVersion: opts.policyVersion,
157
197
  identityInjectionMode: opts.identityInjection?.mode,
158
198
  identityInjectedChars: opts.identityInjection?.injectedChars,
@@ -472,6 +512,7 @@ var LastRecallStore = class {
472
512
  * not need to guard on existence.
473
513
  */
474
514
  async annotateTierExplain(sessionKey, tierExplain, expected) {
515
+ if (isUnsafeStateKey(sessionKey)) return;
475
516
  const current = this.state[sessionKey];
476
517
  if (!current) return;
477
518
  if (!snapshotMatchesExpectedIdentity(current, expected)) return;
@@ -578,7 +619,15 @@ var RecallHandleHistoryStore = class {
578
619
  try {
579
620
  const raw = await readFile(this.statePath, "utf-8");
580
621
  const parsed = JSON.parse(raw);
581
- if (parsed && typeof parsed === "object") this.state = parsed;
622
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
623
+ const next = {};
624
+ for (const [sessionKey, entries] of Object.entries(parsed)) {
625
+ if (isUnsafeStateKey(sessionKey)) continue;
626
+ if (!Array.isArray(entries)) continue;
627
+ next[sessionKey] = entries;
628
+ }
629
+ this.state = next;
630
+ }
582
631
  } catch {
583
632
  this.state = {};
584
633
  }
@@ -588,7 +637,7 @@ var RecallHandleHistoryStore = class {
588
637
  * The ring is capped at {@link maxDepth}; older entries drop off the tail.
589
638
  */
590
639
  async record(sessionKey, memoryIds) {
591
- if (!sessionKey) return;
640
+ if (!sessionKey || isUnsafeStateKey(sessionKey)) return;
592
641
  const ids = memoryIds.filter((id) => typeof id === "string" && id.length > 0);
593
642
  const entry = { at: (/* @__PURE__ */ new Date()).toISOString(), ids };
594
643
  const prior = this.state[sessionKey] ?? [];
@@ -606,6 +655,7 @@ var RecallHandleHistoryStore = class {
606
655
  * Empty array when the session has no recorded history.
607
656
  */
608
657
  recent(sessionKey, depth = this.maxDepth) {
658
+ if (isUnsafeStateKey(sessionKey)) return [];
609
659
  const entries = this.state[sessionKey];
610
660
  if (!entries || entries.length === 0) return [];
611
661
  const limit = Math.max(0, Math.min(depth, entries.length));
@@ -639,4 +689,4 @@ export {
639
689
  TierMigrationStatusStore,
640
690
  RecallHandleHistoryStore
641
691
  };
642
- //# sourceMappingURL=chunk-PRDJMOST.js.map
692
+ //# sourceMappingURL=chunk-ZVZHG7AI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recall-state.ts"],"sourcesContent":["import { appendFile, mkdir, readFile, rename, rm, stat, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { log } from \"./logger.js\";\nimport { writeFileAtomically } from \"./maintenance/atomic-file.js\";\nimport { isErrnoCode } from \"./utils/errno.js\";\nimport { withHeldFileLock, type HeldFileLockOptions } from \"./utils/serialize-mutations.js\";\nimport { ensureContainedSpillDir, listContainedSpillFiles } from \"./utils/path-containment.js\";\nimport type { SearchDegradation } from \"./search/port.js\";\nimport type {\n IdentityInjectionMode,\n RecallPlanMode,\n RecallTierExplain,\n} from \"./types.js\";\nimport {\n coerceIncludedMemories,\n type IncludedMemory,\n} from \"./included-memories.js\";\n\nexport type { IncludedMemory } from \"./included-memories.js\";\nexport { coerceIncludedMemories } from \"./included-memories.js\";\n\n// Session keys are caller-supplied (host session IDs) and land in indexed\n// assignments on plain-object state. A key matching one of these would\n// pollute Object.prototype (or read junk off it), so every read/write\n// through this module rejects them — same contract load() already enforced.\nfunction isUnsafeStateKey(key: string): boolean {\n return key === \"__proto__\" || key === \"constructor\" || key === \"prototype\";\n}\n\n\nexport interface LastRecallBudgetSummary {\n requestedTopK?: number;\n appliedTopK: number;\n recallBudgetChars: number;\n maxMemoryTokens: number;\n qmdFetchLimit?: number;\n qmdHybridFetchLimit?: number;\n finalContextChars?: number;\n truncated?: boolean;\n includedSections?: string[];\n omittedSections?: string[];\n includedMemoryIds?: string[];\n includedMemoryPaths?: string[];\n includedMemoryNamespaces?: Array<string | undefined>;\n omittedMemoryIds?: string[];\n}\n\nexport interface LastRecallSnapshot {\n sessionKey: string;\n recordedAt: string;\n queryHash: string;\n queryLen: number;\n memoryIds: string[];\n includedMemories?: IncludedMemory[];\n namespace?: string;\n recallNamespaces?: string[];\n traceId?: string;\n plannerMode?: RecallPlanMode;\n requestedMode?: RecallPlanMode;\n source?: string;\n fallbackUsed?: boolean;\n sourcesUsed?: string[];\n budgetsApplied?: LastRecallBudgetSummary;\n latencyMs?: number;\n resultPaths?: string[];\n resultNamespaces?: Array<string | undefined>;\n policyVersion?: string;\n identityInjectionMode?: IdentityInjectionMode | \"none\";\n identityInjectedChars?: number;\n identityInjectionTruncated?: boolean;\n /**\n * Collision-safe write nonce. Random UUID set on every `record()`\n * call so the observation-mode direct-answer hook can detect stale\n * snapshots and avoid annotating a snapshot that a subsequent recall\n * already replaced (issue #518).\n */\n writeNonce?: string;\n /**\n * Optional tier-level explanation of how recall was served\n * (issue #518). Populated by orchestrator call sites that can\n * identify a concrete tier; surfaces expose the block via\n * `engram query --explain`, the `?explain=1` HTTP flag, and the\n * `remnic_recall_explain` MCP tool. Orthogonal to the existing\n * graph-path `recallExplain` operation.\n */\n tierExplain?: RecallTierExplain;\n /**\n * Backend degradations observed while serving this recall (issue #1536).\n * Present only when a search backend reported unavailable/loading/timeout\n * during the recall — distinguishing \"no matches\" from \"backend could not\n * answer\" (CLAUDE.md rule 34). Deliberately independent of `tierExplain`,\n * which is gated behind `recallDirectAnswerEnabled`.\n */\n backendDegradations?: SearchDegradation[];\n}\n\nexport interface GraphRecallExpandedEntry {\n path: string;\n score: number;\n namespace: string;\n seed: string;\n hopDepth: number;\n decayedWeight: number;\n graphType: \"entity\" | \"time\" | \"causal\";\n /**\n * Issue #681 PR 3/3 — confidence of the edge that produced this entry's\n * recorded provenance (strongest edge along the chosen entry path).\n * Range `[0, 1]`. Optional so persisted snapshots from older builds\n * round-trip through `clampGraphRecallExpandedEntries` without dropping.\n */\n edgeConfidence?: number;\n /** Graph-relative activation path node ids when provenance is enabled. */\n pathNodeIds?: string[];\n /** True when a graph path penalty was applied. */\n pathPenaltyApplied?: boolean;\n}\n\nexport function clampGraphRecallExpandedEntries(\n entries: unknown,\n maxEntries: number = 64,\n): GraphRecallExpandedEntry[] {\n const limit = Math.max(1, Math.floor(maxEntries));\n if (!Array.isArray(entries)) return [];\n return entries\n .filter((item): item is Record<string, unknown> => !!item && typeof item === \"object\")\n .map((item) => {\n const graphType: \"entity\" | \"time\" | \"causal\" =\n item.graphType === \"entity\" || item.graphType === \"time\" || item.graphType === \"causal\"\n ? item.graphType\n : \"entity\";\n const out: GraphRecallExpandedEntry = {\n path: typeof item.path === \"string\" ? item.path : \"\",\n score: typeof item.score === \"number\" && Number.isFinite(item.score) ? item.score : 0,\n namespace: typeof item.namespace === \"string\" ? item.namespace : \"\",\n seed: typeof item.seed === \"string\" ? item.seed : \"\",\n hopDepth:\n typeof item.hopDepth === \"number\" && Number.isFinite(item.hopDepth)\n ? Math.max(0, Math.floor(item.hopDepth))\n : 0,\n decayedWeight:\n typeof item.decayedWeight === \"number\" && Number.isFinite(item.decayedWeight)\n ? Math.max(0, item.decayedWeight)\n : 0,\n graphType,\n };\n // Issue #681 PR 3/3: clamp `edgeConfidence` into [0, 1] when present.\n // Older snapshots without the field round-trip cleanly via the\n // optional shape; legacy callers always rendered as 1.0.\n if (\n typeof item.edgeConfidence === \"number\" &&\n Number.isFinite(item.edgeConfidence)\n ) {\n out.edgeConfidence = Math.min(1, Math.max(0, item.edgeConfidence));\n }\n if (\n Array.isArray(item.pathNodeIds) &&\n item.pathNodeIds.every((value): value is string => typeof value === \"string\")\n ) {\n out.pathNodeIds = item.pathNodeIds.slice(0, 64);\n }\n if (typeof item.pathPenaltyApplied === \"boolean\") {\n out.pathPenaltyApplied = item.pathPenaltyApplied;\n }\n return out;\n })\n .filter((item) => item.path.length > 0 && item.namespace.length > 0)\n .slice(0, limit);\n}\n\ntype LastRecallState = Record<string, LastRecallSnapshot>;\ntype StateFileWriter = (filePath: string, content: string) => Promise<void>;\n\n/**\n * Deep-copy a RecallTierExplain block. Used by both the write path\n * (so caller mutation after `record()` cannot tear the persisted\n * snapshot) and the read path (so caller mutation after `get()` /\n * `getMostRecent()` cannot tear the in-memory store).\n *\n * Uses structuredClone so future additions to RecallTierExplain do\n * not silently share references through hand-enumerated fields —\n * matching the pattern used elsewhere in the codebase (e.g.,\n * qmd-recall-cache.ts). The payload is pure JSON-shaped data, so\n * structuredClone is both safe and complete here.\n */\n/**\n * Stale-snapshot identity guard shared by the annotate methods (issue #518):\n * a writeNonce match wins outright; otherwise traceId (when expected) and\n * recordedAt are compared. Extracted so every annotation surface applies\n * identical guards (CLAUDE.md rule 22).\n */\nfunction cloneTierExplain(\n tierExplain: RecallTierExplain | undefined,\n): RecallTierExplain | undefined {\n if (!tierExplain) return undefined;\n return structuredClone(tierExplain);\n}\n\nfunction snapshotMatchesExpectedIdentity(\n current: LastRecallSnapshot,\n expected?: { writeNonce?: string; traceId?: string; recordedAt?: string },\n): boolean {\n if (!expected) return true;\n if (typeof expected.writeNonce === \"string\" && expected.writeNonce.length > 0) {\n return current.writeNonce === expected.writeNonce;\n }\n const hasExpectedTraceId =\n typeof expected.traceId === \"string\" && expected.traceId.length > 0;\n if (hasExpectedTraceId && current.traceId !== expected.traceId) return false;\n if (typeof expected.recordedAt === \"string\" && expected.recordedAt.length > 0) {\n return current.recordedAt === expected.recordedAt;\n }\n return true;\n}\n\nfunction hydrateLastRecallSnapshot(snapshot: LastRecallSnapshot): LastRecallSnapshot {\n const includedMemories = coerceIncludedMemories(snapshot);\n const budgets = snapshot.budgetsApplied\n ? {\n ...snapshot.budgetsApplied,\n includedMemoryIds: includedMemories.map((memory) => memory.id),\n includedMemoryPaths: includedMemories.map((memory) => memory.path),\n includedMemoryNamespaces: includedMemories.map((memory) => memory.namespace),\n }\n : undefined;\n return {\n ...snapshot,\n includedMemories,\n memoryIds: includedMemories.map((memory) => memory.id),\n resultPaths: includedMemories.map((memory) => memory.path),\n resultNamespaces: includedMemories.map((memory) => memory.namespace),\n ...(budgets ? { budgetsApplied: budgets } : {}),\n };\n}\n\nfunction cloneLastRecallSnapshot(\n snapshot: LastRecallSnapshot | null,\n): LastRecallSnapshot | null {\n if (!snapshot) return null;\n return structuredClone(hydrateLastRecallSnapshot(snapshot));\n}\n\n\n\nexport interface TierMigrationCycleSummary {\n trigger: \"extraction\" | \"maintenance\" | \"manual\";\n scanned: number;\n migrated: number;\n promoted: number;\n demoted: number;\n limit: number;\n dryRun: boolean;\n skipped?: string;\n errorCount?: number;\n}\n\nexport interface TierMigrationStatusSnapshot {\n updatedAt: string;\n lastCycle: TierMigrationCycleSummary | null;\n totals: {\n cycles: number;\n scanned: number;\n migrated: number;\n promoted: number;\n demoted: number;\n errors: number;\n };\n}\n\nconst DEFAULT_TIER_MIGRATION_STATUS: TierMigrationStatusSnapshot = {\n updatedAt: new Date(0).toISOString(),\n lastCycle: null,\n totals: {\n cycles: 0,\n scanned: 0,\n migrated: 0,\n promoted: 0,\n demoted: 0,\n errors: 0,\n },\n};\n\n/**\n * Suffix marking an impression spill CLAIMED for commit but not yet deleted.\n * The crash-safe drain renames `<uuid>.jsonl` -> `<uuid>.jsonl.claimed` before\n * committing its rows, mirroring the lifecycle ledger's claim/commit protocol\n * (#2033).\n */\nconst CLAIMED_IMPRESSION_SPILL_SUFFIX = \".claimed\";\n\n/**\n * Outcome of {@link LastRecallStore.drainPendingImpressions}. Distinguishes a\n * completed drain (or nothing pending) from a DEFERRED drain that left durable\n * spills in the offline-sync-EXCLUDED queue, so the caller can tell the\n * data-safe cases apart from the one where an offline-sync snapshot would\n * silently omit recorded impressions (#2033).\n */\nexport interface DrainPendingImpressionsResult {\n /** Pending spill rows were folded into the synced active file this call. */\n folded: boolean;\n /**\n * Durable spill rows remain in the offline-sync-EXCLUDED pending queue after\n * this drain: the rotation lock could not be acquired, a spill could not be\n * claimed, or the drain-only fold left rows that would only fit by rotating\n * into a sync-excluded archive. A snapshot taken now would silently omit them\n * (#2033): the caller MUST treat the active file as INCOMPLETE and retry or\n * abort the snapshot rather than report success. `false` means the active file\n * is complete — every pending row was folded or nothing was pending.\n */\n pendingDeferred: boolean;\n}\n\nexport class LastRecallStore {\n private readonly statePath: string;\n private readonly impressionsPath: string;\n private readonly writeStateFile: StateFileWriter;\n private readonly impressionsRotateBytes: number;\n private readonly impressionsRotateKeep: number;\n // Lock-acquisition timing for the cross-process impression rotation lock.\n // Only tests set this (small maxWaitMs) to force the acquisition-timeout\n // (`acquired=false`) branch deterministically; production uses the defaults.\n private readonly impressionsLockOptions?: Pick<HeldFileLockOptions, \"maxWaitMs\" | \"pollMs\">;\n private state: LastRecallState = {};\n private stateWriteChain: Promise<void> = Promise.resolve();\n // Serializes the rotate-then-append critical section so concurrent record()\n // calls never interleave rotation renames with each other's appends (#1910).\n private impressionsWriteChain: Promise<void> = Promise.resolve();\n\n constructor(\n memoryDir: string,\n options: {\n writeStateFile?: StateFileWriter;\n impressionsRotateBytes?: number;\n impressionsRotateKeep?: number;\n impressionsLockOptions?: Pick<HeldFileLockOptions, \"maxWaitMs\" | \"pollMs\">;\n } = {},\n ) {\n this.statePath = path.join(memoryDir, \"state\", \"last_recall.json\");\n this.impressionsPath = path.join(memoryDir, \"state\", \"recall_impressions.jsonl\");\n // 0 disables rotation (never coerced to a default); keep floors at 1.\n this.impressionsRotateBytes = Math.max(0, Math.floor(options.impressionsRotateBytes ?? 0));\n this.impressionsRotateKeep = Math.max(1, Math.floor(options.impressionsRotateKeep ?? 5));\n this.impressionsLockOptions = options.impressionsLockOptions;\n this.writeStateFile =\n options.writeStateFile ??\n (async (filePath, content) => {\n await writeFileAtomically(filePath, content);\n });\n }\n\n async load(): Promise<void> {\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n this.state = {};\n return;\n }\n const next: LastRecallState = {};\n for (const [sessionKey, snapshot] of Object.entries(parsed)) {\n if (sessionKey === \"__proto__\" || sessionKey === \"constructor\" || sessionKey === \"prototype\") {\n continue;\n }\n if (!snapshot || typeof snapshot !== \"object\" || Array.isArray(snapshot)) {\n continue;\n }\n next[sessionKey] = hydrateLastRecallSnapshot(snapshot as LastRecallSnapshot);\n }\n this.state = next;\n } catch {\n this.state = {};\n }\n }\n\n get(sessionKey: string): LastRecallSnapshot | null {\n // Defensive copy: callers must not be able to mutate internal state\n // by reaching into array/object fields on the returned snapshot.\n if (isUnsafeStateKey(sessionKey)) return null;\n return cloneLastRecallSnapshot(this.state[sessionKey] ?? null);\n }\n\n getMostRecent(): LastRecallSnapshot | null {\n const snapshots = Object.values(this.state);\n if (snapshots.length === 0) return null;\n // Secondary key on sessionKey keeps the sort stable when two\n // snapshots share a recordedAt timestamp (CLAUDE.md rule 19).\n snapshots.sort((a, b) => {\n const byTime = b.recordedAt.localeCompare(a.recordedAt);\n if (byTime !== 0) return byTime;\n return a.sessionKey.localeCompare(b.sessionKey);\n });\n return cloneLastRecallSnapshot(snapshots[0] ?? null);\n }\n\n /**\n * Persist last-recall snapshot and append an impression log entry.\n * Does not store raw query text; uses a stable hash for correlation.\n */\n async record(opts: {\n sessionKey: string;\n query: string;\n memoryIds?: string[];\n includedMemories?: IncludedMemory[];\n namespace?: string;\n recallNamespaces?: string[];\n traceId?: string;\n plannerMode?: RecallPlanMode;\n requestedMode?: RecallPlanMode;\n source?: string;\n fallbackUsed?: boolean;\n sourcesUsed?: string[];\n budgetsApplied?: LastRecallBudgetSummary;\n latencyMs?: number;\n resultPaths?: string[];\n resultNamespaces?: Array<string | undefined>;\n policyVersion?: string;\n appendImpression?: boolean;\n identityInjection?: {\n mode: IdentityInjectionMode | \"none\";\n injectedChars: number;\n truncated: boolean;\n };\n /**\n * Per-tier explain annotation (issue #518). When supplied, the\n * snapshot carries it so downstream surfaces (CLI / HTTP / MCP)\n * can render which retrieval tier served the query.\n */\n tierExplain?: RecallTierExplain;\n /**\n * Backend degradations observed while serving this recall (issue #1536).\n * Passed at record time so the published snapshot is born annotated —\n * a post-record annotation would leave a window where readers see the\n * snapshot without them (codex review on #1544).\n */\n backendDegradations?: SearchDegradation[];\n }): Promise<void> {\n if (isUnsafeStateKey(opts.sessionKey)) {\n log.debug(\"last recall record skipped: unsafe session key\");\n return;\n }\n const now = new Date().toISOString();\n const queryHash = createHash(\"sha256\").update(opts.query).digest(\"hex\");\n\n // Build the snapshot from opts, then deep-copy it via\n // cloneLastRecallSnapshot so caller arrays/objects passed in\n // `opts` cannot retain a live reference to the persisted state and\n // tear it after `record()` returns.\n const includedMemories = coerceIncludedMemories(opts);\n const liveSnapshot: LastRecallSnapshot = {\n sessionKey: opts.sessionKey,\n recordedAt: now,\n queryHash,\n writeNonce: randomUUID(),\n queryLen: opts.query.length,\n includedMemories,\n memoryIds: includedMemories.map((memory) => memory.id),\n namespace: opts.namespace,\n recallNamespaces: opts.recallNamespaces,\n traceId: opts.traceId,\n plannerMode: opts.plannerMode,\n requestedMode: opts.requestedMode,\n source: opts.source,\n fallbackUsed: opts.fallbackUsed,\n sourcesUsed: opts.sourcesUsed,\n budgetsApplied: opts.budgetsApplied,\n latencyMs: opts.latencyMs,\n policyVersion: opts.policyVersion,\n identityInjectionMode: opts.identityInjection?.mode,\n identityInjectedChars: opts.identityInjection?.injectedChars,\n identityInjectionTruncated: opts.identityInjection?.truncated,\n tierExplain: opts.tierExplain,\n backendDegradations:\n opts.backendDegradations && opts.backendDegradations.length > 0\n ? opts.backendDegradations\n : undefined,\n };\n // `cloneLastRecallSnapshot` handles `null` but that never applies\n // at this call site — the non-null assertion keeps the type\n // checker honest.\n const snapshot = cloneLastRecallSnapshot(liveSnapshot)!;\n\n this.state[opts.sessionKey] = snapshot;\n\n // Keep the state bounded; the impression log is append-only.\n const keys = Object.keys(this.state);\n if (keys.length > 50) {\n const ordered = keys\n .map((k) => ({ k, at: this.state[k]?.recordedAt ?? \"\" }))\n .sort((a, b) => b.at.localeCompare(a.at));\n for (const doomed of ordered.slice(50)) {\n delete this.state[doomed.k];\n }\n }\n\n try {\n await this.flushState();\n } catch (err) {\n log.debug(`last recall store write failed: ${err}`);\n }\n\n if (opts.appendImpression !== false) {\n const line = JSON.stringify(snapshot) + \"\\n\";\n // Chain onto the previous impression write so rotation + append run as one\n // serialized critical section — concurrent record() calls can never\n // interleave a rename with another call's append (#1910). `.catch` before\n // the link keeps a prior failure from poisoning the chain.\n const chained = this.impressionsWriteChain\n .catch(() => {})\n .then(() => this.appendImpressionSerialized(line));\n this.impressionsWriteChain = chained;\n try {\n await chained;\n } catch (err) {\n log.debug(`recall impressions append failed: ${err}`);\n }\n }\n }\n\n /**\n * Drain pending spills, rotate (best-effort, sized to the projected payload),\n * then append one impression line under ONE shared cross-process lock (#1910,\n * #2033). Both the archive-shift renames AND the append run inside the same\n * held lock so a peer process holding the lock can never rename the active\n * inode to `.1` between our open and write — which would land the impression in\n * a rotated archive that offline-sync excludes, silently dropping it from\n * active-state/sync consumers.\n *\n * On lock-acquisition timeout the impression is NEVER written to the active\n * file unlocked (#2033): a peer holding the lock is likely mid-rotation, so an\n * unlocked append can land in the inode it is about to rename to `.1` — the\n * exact silent-drop this lock prevents. Instead the row is spilled to a\n * durable node-local per-event file under `<impressions>.pending.d/` and\n * folded back into the active file by the next append that DOES hold the lock,\n * preserving durability without racing rotation. Rotation failure under the\n * held lock is logged and the append still runs. Callers invoke this only\n * through `impressionsWriteChain` so the sequence is serialized in-process.\n */\n private get impressionsPendingDir(): string {\n return `${this.impressionsPath}.pending.d`;\n }\n\n private async spillImpression(line: string): Promise<void> {\n await ensureContainedSpillDir(this.impressionsPendingDir);\n // Write to a temp name the drain's `.jsonl` lister ignores, then atomically\n // rename to the final `<uuid>.jsonl` (#2033). writeFile creates the path\n // before its bytes are durable, so writing straight to the final name lets a\n // concurrent lock holder's drain read/rename/delete a partial spill and\n // append malformed JSON — permanently losing that impression. The temp lives\n // in the SAME contained dir, so the rename is atomic and stays symlink-safe.\n const id = randomUUID();\n const tempPath = path.join(this.impressionsPendingDir, `${id}.jsonl.tmp`);\n const finalPath = path.join(this.impressionsPendingDir, `${id}.jsonl`);\n await writeFile(tempPath, line, \"utf-8\");\n await rename(tempPath, finalPath);\n }\n\n /**\n * Recover a claim a crashed drain left mid-flight (#2033): the crash-safe\n * drain renames `<uuid>.jsonl` -> `<uuid>.jsonl.claimed` BEFORE committing, so\n * a `.claimed` file with no matching commit is the ONLY durable copy of those\n * rows. Rename each orphan back to `<uuid>.jsonl` so it re-enters the normal\n * collect+claim+commit flow. MUST run under the held rotation lock, before\n * collecting. listContainedSpillFiles skips symlinked/escaping entries.\n */\n private async recoverOrphanedImpressionClaims(): Promise<void> {\n const orphans = await listContainedSpillFiles(\n this.impressionsPendingDir,\n `.jsonl${CLAIMED_IMPRESSION_SPILL_SUFFIX}`,\n );\n for (const claimedPath of orphans) {\n const original = claimedPath.slice(0, -CLAIMED_IMPRESSION_SPILL_SUFFIX.length);\n await rename(claimedPath, original).catch(() => undefined);\n }\n }\n\n /**\n * Recover a crash-orphaned spill temp after its write completed but before\n * the atomic `.tmp` → `.jsonl` rename. Valid JSONL temps are promoted back\n * into the normal queue; incomplete temps remain pending and keep offline sync\n * deferred rather than being discarded.\n */\n private async recoverOrphanedImpressionTemps(): Promise<void> {\n const temps = await listContainedSpillFiles(this.impressionsPendingDir, \".jsonl.tmp\");\n for (const tempPath of temps) {\n let content: string;\n try {\n content = await readFile(tempPath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n if (line.trim().length > 0) JSON.parse(line);\n }\n } catch {\n continue;\n }\n const finalPath = tempPath.slice(0, -\".tmp\".length);\n await rename(tempPath, finalPath).catch(() => undefined);\n }\n }\n\n private async appendImpressionSerialized(line: string): Promise<void> {\n await mkdir(path.dirname(this.impressionsPath), { recursive: true });\n // Every active-file write goes under the shared cross-process rotation lock,\n // even when THIS writer's local rotation is disabled (`impressionsRotateBytes\n // <= 0`). A peer process with rotation enabled shares this lock and may\n // rename the active inode to `.1` at any moment, so an unlocked append could\n // land in an offline-sync-excluded archive and be silently dropped (#2033).\n // Only the local rotate DECISION is disabled — the bounded fold skips\n // rotation when this writer's limit is 0 — the lock itself is not.\n const lockPath = `${this.impressionsPath}.lock`;\n await withHeldFileLock(\n lockPath,\n { staleMs: 30_000, ...this.impressionsLockOptions },\n async (acquired) => {\n // withHeldFileLock falls back to task(false) when it cannot acquire the\n // lock within the budget. Do NOT append to the active file unlocked: a\n // peer holding the lock may be mid-rotation, so an unlocked append could\n // land in the inode it renames to `.1` (an offline-sync-excluded\n // archive), silently dropping the impression. Spill it to the durable\n // pending queue instead — the next lock holder folds it back in (#2033).\n if (!acquired) {\n log.debug(\"recall impressions rotation lock not acquired; spilling impression to the durable pending queue\");\n await this.spillImpression(line);\n return;\n }\n await this.foldPendingImpressionsAndAppend(line);\n },\n );\n }\n\n /**\n * Fold any durable pending recall-impression spills into the synced active\n * `recall_impressions.jsonl` WITHOUT recording a new impression (#2033).\n *\n * The pending spill dir is offline-sync-excluded, so an impression that a\n * lock-timed-out `record()` spilled there is absent from an offline-sync\n * snapshot and lost if this node is discarded before the next `record()`\n * folds it back. Offline sync calls this before building a snapshot so a\n * recorded impression always reaches the remote. Fast no-op — no lock and no\n * dir/lock-file creation — when nothing is pending.\n *\n * Returns a {@link DrainPendingImpressionsResult}: `folded` is true when rows\n * were folded into the active file; `pendingDeferred` is true when durable\n * spills still remain in the offline-sync-EXCLUDED queue after this call —\n * because the rotation lock could not be acquired, a spill could not be\n * claimed, or the drain-only fold deferred rows that would only fit by\n * rotating into a sync-excluded archive (#2033). A snapshot taken while\n * `pendingDeferred` is true would silently drop those rows, so the caller MUST\n * NOT report a clean snapshot — it either retries or aborts.\n */\n async drainPendingImpressions(): Promise<DrainPendingImpressionsResult> {\n if (!(await this.pendingSpillsRemain())) {\n return { folded: false, pendingDeferred: false };\n }\n const lockPath = `${this.impressionsPath}.lock`;\n let folded = false;\n let acquiredLock = false;\n let spillsRemain = false;\n await withHeldFileLock(\n lockPath,\n { staleMs: 30_000, ...this.impressionsLockOptions },\n async (acquired) => {\n // A peer holds the lock (mid-rotation or mid-append). We MUST NOT touch\n // the active file unlocked (#2033), so the spills stay in the\n // offline-sync-EXCLUDED pending queue. Report the drain as DEFERRED\n // (pendingDeferred=true) so the caller does not build a snapshot that\n // silently omits them; the next lock holder — or a caller retry — folds\n // them in.\n if (!acquired) return;\n acquiredLock = true;\n folded = await this.foldPendingImpressionsAndAppend(null);\n // Even WITH the lock, a spill the fold could not claim or a `.claimed`\n // orphan a failed commit left behind keeps durable rows in the excluded\n // queue. Treat that as an incomplete drain so the caller retries/aborts\n // rather than snapshotting an active file that omits them (#2033).\n spillsRemain = await this.pendingSpillsRemain();\n },\n );\n return { folded, pendingDeferred: !acquiredLock || spillsRemain };\n }\n\n /**\n * Claim/commit fold of durable pending impression spills into the active file\n * under the held rotation lock (#2033). `line` is a new impression to append\n * after the drained rows, or null for a drain-only pre-sync fold.\n *\n * Each spill is folded as an INDEPENDENT claim -> append -> finalize unit\n * rather than materializing the whole queue as one joined payload and\n * splitting it in a single synchronous pass (#2033). A multi-hundred-MB drain\n * done as one join/split blocks the event loop long enough for\n * `withHeldFileLock`'s ownership-refresh timer to miss its window, so a peer\n * stale-breaks the 30s lock and rotates/appends concurrently — defeating the\n * rotation lock this drain relies on. The per-file `await`s keep the payload\n * bounded to one spill at a time and let the refresh timer fire between files.\n *\n * The DRAIN-ONLY path (`line === null`, run before an offline-sync snapshot)\n * NEVER rotates a drained row into an archive. `recall_impressions.jsonl.*`\n * is offline-sync-EXCLUDED, so rotating a just-drained row into `.1`/`.2` and\n * then deleting its spill would leave that row only in a path sync omits —\n * silently lost when the node is discarded after a \"successful\" sync (#2033).\n * It folds only the rows that fit the synced active file under the cap and\n * leaves the rest durable in the pending queue; {@link drainPendingImpressions}\n * then reports the drain DEFERRED so the caller retries or aborts. The\n * record() path (`line !== null`) keeps rotating normally — its archives stay\n * on a live node that is not being discarded.\n *\n * Recovers any crash-orphaned claim first. Each impression carries a unique\n * writeNonce, so a crash-recovered re-commit is a collapsible duplicate, never\n * a lost row. listContainedSpillFiles rejects symlinked/escaping entries; a\n * spill that cannot be claimed is skipped this pass. An append failure rolls\n * that spill's claim back so the row retries on the next lock holder's drain.\n * Returns true when the active file was written. MUST run under the held\n * rotation lock.\n */\n private async foldPendingImpressionsAndAppend(line: string | null): Promise<boolean> {\n await this.recoverOrphanedImpressionTemps();\n await this.recoverOrphanedImpressionClaims();\n const spillFiles = await listContainedSpillFiles(this.impressionsPendingDir);\n const drainOnly = line === null;\n const cap = this.impressionsRotateBytes;\n let activeSize = 0;\n if (cap > 0) {\n try {\n activeSize = (await stat(this.impressionsPath)).size;\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n }\n }\n let wrote = false;\n for (const filePath of spillFiles) {\n const content = await readFile(filePath, \"utf-8\");\n const unit = content.length === 0 || content.endsWith(\"\\n\") ? content : `${content}\\n`;\n const unitBytes = Buffer.byteLength(unit, \"utf-8\");\n // Drain-only fold must never push a drained row into a sync-excluded\n // archive (#2033). Once the active file cannot take this spill under the\n // cap without rotating, stop: the remaining spills stay durable in the\n // pending queue and the drain reports itself deferred. A lone spill larger\n // than the cap on an empty active file is still folded whole (it cannot be\n // split without corrupting JSONL) so it reaches sync rather than stranding.\n if (drainOnly && cap > 0 && activeSize > 0 && activeSize + unitBytes >= cap) break;\n const claimedPath = `${filePath}${CLAIMED_IMPRESSION_SPILL_SUFFIX}`;\n try {\n await rename(filePath, claimedPath);\n } catch {\n continue; // could not claim → do not commit; a later drain retries it.\n }\n try {\n activeSize = await this.appendImpressionUnit(unit, activeSize, drainOnly);\n } catch (err) {\n // Commit failed: roll this spill's claim back so it retries on the next\n // lock holder's drain instead of being lost.\n await rename(claimedPath, filePath).catch(() => undefined);\n throw err;\n }\n // Commit is durable: delete the claimed file. A delete that fails leaves a\n // `.claimed` orphan the next drain recovers and re-commits — a\n // writeNonce-collapsible duplicate, never a lost row (#2033).\n await unlink(claimedPath).catch(() => undefined);\n wrote = true;\n }\n if (line !== null) {\n activeSize = await this.appendImpressionUnit(line, activeSize, false);\n wrote = true;\n }\n return wrote;\n }\n\n /**\n * Append one whole JSONL unit (a claimed spill's rows or a fresh impression\n * line) to the active impressions file, returning the resulting active-file\n * byte size. On the rotating (record) path, when the unit would push the\n * current active generation to/over `impressionsRotateBytes`, the full active\n * file is rotated aside FIRST so no generation exceeds the cap (#2033); a lone\n * unit larger than the cap on an empty active file is written whole because\n * splitting a JSONL row would corrupt it. `drainOnly` and disabled rotation\n * (`impressionsRotateBytes <= 0`) both skip rotation — drain-only defers its\n * overflow in {@link foldPendingImpressionsAndAppend} rather than rotating into\n * a sync-excluded archive. A rotation error is logged and the append still\n * runs so a row is never lost; an append failure propagates so the caller\n * rolls its claim back. MUST run under the held rotation lock.\n */\n private async appendImpressionUnit(\n unit: string,\n activeSize: number,\n drainOnly: boolean,\n ): Promise<number> {\n const cap = this.impressionsRotateBytes;\n const unitBytes = Buffer.byteLength(unit, \"utf-8\");\n if (cap > 0 && !drainOnly && activeSize > 0 && activeSize + unitBytes >= cap) {\n try {\n await this.shiftImpressionArchives();\n activeSize = 0;\n } catch (err) {\n log.debug(`recall impressions rotation failed (append preserved): ${err}`);\n }\n }\n await appendFile(this.impressionsPath, unit, \"utf-8\");\n return activeSize + unitBytes;\n }\n\n /**\n * True when any live `<uuid>.jsonl` spill, crash-orphaned `.claimed` file,\n * or crash-orphaned `.jsonl.tmp` spill remains in the offline-sync-EXCLUDED\n * pending queue (#2033). After a drain acquires the rotation lock, a spill\n * the fold could not claim (rename race / read-only dir) or a `.claimed`\n * orphan a failed commit left behind means durable rows are STILL excluded\n * from a snapshot — the drain must report itself INCOMPLETE so the caller\n * retries or aborts rather than snapshotting an active file that omits them.\n */\n private async pendingSpillsRemain(): Promise<boolean> {\n if ((await listContainedSpillFiles(this.impressionsPendingDir, \".jsonl.tmp\")).length > 0) {\n return true;\n }\n const liveSpills = await listContainedSpillFiles(this.impressionsPendingDir);\n if (liveSpills.length > 0) return true;\n const claimed = await listContainedSpillFiles(\n this.impressionsPendingDir,\n `.jsonl${CLAIMED_IMPRESSION_SPILL_SUFFIX}`,\n );\n return claimed.length > 0;\n }\n\n /**\n * Shift `.1..N` archives down one slot and move the active file to `.1`,\n * dropping anything beyond `keep`. Callers invoke this only while holding the\n * cross-process rotation lock (issue #1910).\n */\n private async shiftImpressionArchives(): Promise<void> {\n const keep = this.impressionsRotateKeep;\n // Drop the archive that would fall off the end, then shift down: .(keep-1)\n // -> .keep, ..., .1 -> .2, active -> .1.\n try {\n await rm(`${this.impressionsPath}.${keep}`, { force: true });\n } catch {\n // best-effort cleanup of the oldest archive\n }\n for (let i = keep - 1; i >= 1; i -= 1) {\n try {\n await rename(`${this.impressionsPath}.${i}`, `${this.impressionsPath}.${i + 1}`);\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n }\n }\n try {\n await rename(this.impressionsPath, `${this.impressionsPath}.1`);\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n }\n }\n\n /**\n * Attach a RecallTierExplain block to the existing snapshot for a\n * session without rewriting the entire snapshot. Used by the\n * post-recall direct-answer annotation path (issue #518 slice 3c):\n * recallInternal records the snapshot first, then the orchestrator\n * fires the direct-answer tier in observation mode and annotates\n * the stored snapshot with whichever tier served the query.\n *\n * No-op when no snapshot exists for the given session; callers do\n * not need to guard on existence.\n */\n async annotateTierExplain(\n sessionKey: string,\n tierExplain: RecallTierExplain,\n expected?: { writeNonce?: string; traceId?: string; recordedAt?: string },\n ): Promise<void> {\n if (isUnsafeStateKey(sessionKey)) return;\n const current = this.state[sessionKey];\n if (!current) return;\n if (!snapshotMatchesExpectedIdentity(current, expected)) return;\n this.state[sessionKey] = {\n ...current,\n tierExplain: cloneTierExplain(tierExplain),\n };\n try {\n await this.flushState();\n } catch (err) {\n log.debug(`last recall tier-explain annotate failed: ${err}`);\n }\n }\n\n\n private flushState(): Promise<void> {\n const run = this.stateWriteChain.catch(() => undefined).then(async () => {\n await mkdir(path.dirname(this.statePath), { recursive: true });\n await this.writeStateFile(this.statePath, JSON.stringify(this.state, null, 2));\n });\n this.stateWriteChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n}\n\nexport class TierMigrationStatusStore {\n private readonly statePath: string;\n private state: TierMigrationStatusSnapshot = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);\n\n constructor(memoryDir: string) {\n this.statePath = path.join(memoryDir, \"state\", \"tier-migration-status.json\");\n }\n\n async load(): Promise<void> {\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<TierMigrationStatusSnapshot> | null;\n if (!parsed || typeof parsed !== \"object\") {\n this.state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);\n return;\n }\n const totals = parsed.totals && typeof parsed.totals === \"object\"\n ? parsed.totals\n : DEFAULT_TIER_MIGRATION_STATUS.totals;\n this.state = {\n updatedAt:\n typeof parsed.updatedAt === \"string\" && parsed.updatedAt.length > 0\n ? parsed.updatedAt\n : DEFAULT_TIER_MIGRATION_STATUS.updatedAt,\n lastCycle:\n parsed.lastCycle && typeof parsed.lastCycle === \"object\"\n ? (parsed.lastCycle as TierMigrationCycleSummary)\n : null,\n totals: {\n cycles: typeof totals.cycles === \"number\" && Number.isFinite(totals.cycles) ? totals.cycles : 0,\n scanned: typeof totals.scanned === \"number\" && Number.isFinite(totals.scanned) ? totals.scanned : 0,\n migrated: typeof totals.migrated === \"number\" && Number.isFinite(totals.migrated) ? totals.migrated : 0,\n promoted: typeof totals.promoted === \"number\" && Number.isFinite(totals.promoted) ? totals.promoted : 0,\n demoted: typeof totals.demoted === \"number\" && Number.isFinite(totals.demoted) ? totals.demoted : 0,\n errors: typeof totals.errors === \"number\" && Number.isFinite(totals.errors) ? totals.errors : 0,\n },\n };\n } catch {\n this.state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);\n }\n }\n\n get(): TierMigrationStatusSnapshot {\n return {\n updatedAt: this.state.updatedAt,\n lastCycle: this.state.lastCycle ? { ...this.state.lastCycle } : null,\n totals: { ...this.state.totals },\n };\n }\n\n async recordCycle(summary: TierMigrationCycleSummary): Promise<void> {\n const now = new Date().toISOString();\n const migratedDelta = summary.dryRun ? 0 : Math.max(0, summary.migrated);\n const promotedDelta = summary.dryRun ? 0 : Math.max(0, summary.promoted);\n const demotedDelta = summary.dryRun ? 0 : Math.max(0, summary.demoted);\n const next: TierMigrationStatusSnapshot = {\n updatedAt: now,\n lastCycle: { ...summary },\n totals: {\n cycles: this.state.totals.cycles + 1,\n scanned: this.state.totals.scanned + Math.max(0, summary.scanned),\n migrated: this.state.totals.migrated + migratedDelta,\n promoted: this.state.totals.promoted + promotedDelta,\n demoted: this.state.totals.demoted + demotedDelta,\n errors: this.state.totals.errors + Math.max(0, summary.errorCount ?? 0),\n },\n };\n this.state = next;\n try {\n await mkdir(path.dirname(this.statePath), { recursive: true });\n await writeFile(this.statePath, JSON.stringify(next, null, 2), \"utf-8\");\n } catch (err) {\n log.debug(`tier migration status write failed: ${err}`);\n }\n }\n}\n\n\n/**\n * Per-session bounded history of recent recall memory-id sets, used ONLY by\n * handle resolution (issue #1582). Distinct from {@link LastRecallStore} —\n * that keeps ONE last snapshot per session; this keeps a small ring (depth N)\n * of the memory-id arrays a session has recalled, newest first, so a handle\n * cited in a later turn still resolves even after a newer recall displaced the\n * \"last\" snapshot.\n *\n * Deliberately lightweight: stores only `string[]` per entry (no full\n * snapshot), capped at `depth` per session and 50 sessions total (rule 27\n * slice/cap guards). Misses are acceptable and tagged at resolution time.\n */\nexport class RecallHandleHistoryStore {\n private readonly statePath: string;\n private readonly writeStateFile: StateFileWriter;\n private state: Record<string, Array<{ at: string; ids: string[] }>> = {};\n private writeChain: Promise<void> = Promise.resolve();\n private readonly maxDepth: number;\n\n constructor(memoryDir: string, options: { writeStateFile?: StateFileWriter; maxDepth?: number } = {}) {\n this.statePath = path.join(memoryDir, \"state\", \"handle_history.json\");\n this.writeStateFile =\n options.writeStateFile ??\n (async (filePath, content) => {\n await writeFileAtomically(filePath, content);\n });\n this.maxDepth =\n typeof options.maxDepth === \"number\" && Number.isFinite(options.maxDepth)\n ? Math.max(1, Math.min(50, Math.floor(options.maxDepth)))\n : 5;\n }\n\n async load(): Promise<void> {\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n const next: Record<string, Array<{ at: string; ids: string[] }>> = {};\n for (const [sessionKey, entries] of Object.entries(parsed)) {\n if (isUnsafeStateKey(sessionKey)) continue;\n if (!Array.isArray(entries)) continue;\n next[sessionKey] = entries;\n }\n this.state = next;\n }\n } catch {\n this.state = {};\n }\n }\n\n /**\n * Record one recall's admitted memory ids for a session, newest first.\n * The ring is capped at {@link maxDepth}; older entries drop off the tail.\n */\n async record(sessionKey: string, memoryIds: readonly string[]): Promise<void> {\n if (!sessionKey || isUnsafeStateKey(sessionKey)) return;\n const ids = memoryIds.filter((id): id is string => typeof id === \"string\" && id.length > 0);\n const entry = { at: new Date().toISOString(), ids };\n const prior = this.state[sessionKey] ?? [];\n const next = [entry, ...prior].slice(0, this.maxDepth);\n this.state[sessionKey] = next;\n this.capSessions();\n try {\n await this.flush();\n } catch (err) {\n log.debug(`recall handle history write failed: ${err}`);\n }\n }\n\n /**\n * Return the memory-id arrays for a session, newest first, capped at `depth`.\n * Empty array when the session has no recorded history.\n */\n recent(sessionKey: string, depth: number = this.maxDepth): Array<readonly string[]> {\n if (isUnsafeStateKey(sessionKey)) return [];\n const entries = this.state[sessionKey];\n if (!entries || entries.length === 0) return [];\n const limit = Math.max(0, Math.min(depth, entries.length));\n return entries.slice(0, limit).map((e) => e.ids);\n }\n\n /** Test seam: drop all history (used by the corpus-scan hygiene test). */\n clearForTest(): void {\n this.state = {};\n }\n\n private capSessions(): void {\n const keys = Object.keys(this.state);\n if (keys.length <= 50) return;\n const ordered = keys\n .map((k) => ({ k, at: this.state[k]?.[0]?.at ?? \"\" }))\n .sort((a, b) => b.at.localeCompare(a.at));\n for (const doomed of ordered.slice(50)) {\n delete this.state[doomed.k];\n }\n }\n\n private flush(): Promise<void> {\n const run = this.writeChain.catch(() => undefined).then(async () => {\n await mkdir(path.dirname(this.statePath), { recursive: true });\n await this.writeStateFile(this.statePath, JSON.stringify(this.state, null, 2));\n });\n this.writeChain = run.then(() => undefined, () => undefined);\n return run;\n }\n}\n"],"mappings":"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","names":[]}