@remnic/core 9.7.15 → 9.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (929) hide show
  1. package/dist/access-admin-ops-surface.d.ts +13 -6
  2. package/dist/access-admin-ops-surface.js +85 -71
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-boundary.d.ts +35 -17
  5. package/dist/access-boundary.js +86 -74
  6. package/dist/access-citation.d.ts +28 -0
  7. package/dist/access-citation.js +9 -0
  8. package/dist/access-cli.js +194 -178
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-errors.d.ts +16 -1
  11. package/dist/access-errors.js +7 -3
  12. package/dist/access-http.d.ts +25 -19
  13. package/dist/access-http.js +100 -87
  14. package/dist/access-identity-continuity-surface.d.ts +13 -8
  15. package/dist/access-identity-continuity-surface.js +85 -71
  16. package/dist/access-lcm-surface.d.ts +16 -9
  17. package/dist/access-lcm-surface.js +85 -71
  18. package/dist/access-mcp.d.ts +23 -16
  19. package/dist/access-mcp.js +96 -84
  20. package/dist/access-memory-search-fanout.d.ts +93 -0
  21. package/dist/access-memory-search-fanout.js +18 -0
  22. package/dist/access-observe-write-surface.d.ts +26 -8
  23. package/dist/access-observe-write-surface.js +85 -71
  24. package/dist/access-operations-batch.js +92 -80
  25. package/dist/access-operations.d.ts +29 -22
  26. package/dist/access-operations.js +94 -82
  27. package/dist/access-recall-concurrency.d.ts +250 -0
  28. package/dist/access-recall-concurrency.js +157 -0
  29. package/dist/access-recall-response.d.ts +130 -0
  30. package/dist/access-recall-response.js +147 -0
  31. package/dist/access-recall-surface.d.ts +15 -7
  32. package/dist/access-recall-surface.js +85 -71
  33. package/dist/access-schema.d.ts +98 -98
  34. package/dist/access-schema.js +8 -6
  35. package/dist/{access-service-DpcYZ9Nl.d.ts → access-service-DbZ1G3zV.d.ts} +29 -36
  36. package/dist/access-service.d.ts +13 -6
  37. package/dist/access-service.js +89 -72
  38. package/dist/access-surface-catalog.d.ts +23 -16
  39. package/dist/access-surface-catalog.js +1 -0
  40. package/dist/access-surface-catalog.js.map +1 -1
  41. package/dist/access-token-capabilities.d.ts +2 -1
  42. package/dist/access-token-capabilities.js +4 -2
  43. package/dist/action-confidence.d.ts +3 -1
  44. package/dist/active-memory-bridge.d.ts +3 -1
  45. package/dist/active-memory-bridge.js +4 -4
  46. package/dist/active-recall.d.ts +4 -1
  47. package/dist/active-recall.js +23 -13
  48. package/dist/active-recall.js.map +1 -1
  49. package/dist/adapters/index.js +4 -4
  50. package/dist/adapters/registry.js +2 -2
  51. package/dist/{auto-sync-AAMTVZMK.js → auto-sync-UZG3XQZ5.js} +14 -8
  52. package/dist/{auto-sync-AAMTVZMK.js.map → auto-sync-UZG3XQZ5.js.map} +1 -1
  53. package/dist/behavior-learner.d.ts +3 -1
  54. package/dist/behavior-signals.d.ts +3 -1
  55. package/dist/bootstrap.d.ts +12 -7
  56. package/dist/bootstrap.js +2 -2
  57. package/dist/bounded-jsonl-state.d.ts +54 -0
  58. package/dist/bounded-jsonl-state.js +10 -0
  59. package/dist/boxes.js +2 -2
  60. package/dist/briefing.d.ts +5 -2
  61. package/dist/briefing.js +34 -24
  62. package/dist/buffer-surprise-report.d.ts +3 -1
  63. package/dist/buffer.d.ts +60 -2
  64. package/dist/buffer.js +3 -3
  65. package/dist/bulk-import/index.d.ts +5 -3
  66. package/dist/calibration.d.ts +3 -1
  67. package/dist/calibration.js +6 -6
  68. package/dist/capabilities.d.ts +7 -7
  69. package/dist/capabilities.js +1 -1
  70. package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-YO5QJ6L3.js} +3 -3
  71. package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
  72. package/dist/{catalog-C9444P10.d.ts → catalog-BWqHb5HT.d.ts} +1 -1
  73. package/dist/causal-behavior.d.ts +3 -1
  74. package/dist/causal-behavior.js +6 -6
  75. package/dist/causal-chain.js +4 -4
  76. package/dist/causal-consolidation.d.ts +3 -1
  77. package/dist/causal-consolidation.js +43 -34
  78. package/dist/causal-consolidation.js.map +1 -1
  79. package/dist/causal-retrieval.js +6 -6
  80. package/dist/causal-trajectory-graph.d.ts +3 -1
  81. package/dist/causal-trajectory-graph.js +1 -1
  82. package/dist/causal-trajectory.js +1 -1
  83. package/dist/{chunk-A3GOMRJG.js → chunk-23L3IXFD.js} +2 -2
  84. package/dist/chunk-2CGLBUW3.js +15 -0
  85. package/dist/chunk-2CGLBUW3.js.map +1 -0
  86. package/dist/{chunk-MRHFJV44.js → chunk-2DQ2XREU.js} +2 -2
  87. package/dist/{chunk-L2B3EE7B.js → chunk-2QMKMVHH.js} +2 -2
  88. package/dist/{chunk-VJVNLNDQ.js → chunk-2UFO7X4Q.js} +7 -7
  89. package/dist/{chunk-2MR3MFQB.js → chunk-2VEOKUPP.js} +3 -3
  90. package/dist/chunk-2Z24E5W2.js +22 -0
  91. package/dist/chunk-2Z24E5W2.js.map +1 -0
  92. package/dist/{chunk-RHOZ5LLO.js → chunk-347WD2LE.js} +2 -2
  93. package/dist/{chunk-HDTQVNWD.js → chunk-3QAH2S33.js} +3 -3
  94. package/dist/{chunk-37CTGERM.js → chunk-3WCASNQZ.js} +12 -1
  95. package/dist/chunk-3WCASNQZ.js.map +1 -0
  96. package/dist/{chunk-LRNMRXJO.js → chunk-3Y224NYC.js} +3 -3
  97. package/dist/{chunk-3IY64MV6.js → chunk-44XZ75OQ.js} +44 -10
  98. package/dist/chunk-44XZ75OQ.js.map +1 -0
  99. package/dist/{chunk-VFSP73LP.js → chunk-45GF7Y3V.js} +2 -2
  100. package/dist/{chunk-K7LOWSLU.js → chunk-4BJQODZF.js} +2479 -1520
  101. package/dist/chunk-4BJQODZF.js.map +1 -0
  102. package/dist/{chunk-JREB6UFH.js → chunk-57OWKFUQ.js} +17 -6
  103. package/dist/chunk-57OWKFUQ.js.map +1 -0
  104. package/dist/{chunk-OEHCMALU.js → chunk-5I4M26Q7.js} +5 -5
  105. package/dist/{chunk-2AZXIZEN.js → chunk-5Q7EVV4X.js} +6 -6
  106. package/dist/{chunk-LC7OODWZ.js → chunk-5SB7SC2G.js} +80 -49
  107. package/dist/chunk-5SB7SC2G.js.map +1 -0
  108. package/dist/{chunk-PUBTHW5O.js → chunk-6DRPPQEJ.js} +2 -2
  109. package/dist/{chunk-NWTGD4AN.js → chunk-6KIQT7RG.js} +10 -9
  110. package/dist/chunk-6KIQT7RG.js.map +1 -0
  111. package/dist/{chunk-FDCTQWLW.js → chunk-6O22TZSB.js} +3 -3
  112. package/dist/{chunk-ZQ7UBUZ6.js → chunk-6QYST5XN.js} +6 -6
  113. package/dist/{chunk-W4PGTAS2.js → chunk-6WAFYNVT.js} +3 -3
  114. package/dist/chunk-6ZMAMSHS.js +20 -0
  115. package/dist/chunk-6ZMAMSHS.js.map +1 -0
  116. package/dist/{chunk-R5VAVVHP.js → chunk-72A4L5JJ.js} +2 -2
  117. package/dist/{chunk-X64YLC24.js → chunk-72VAXGEI.js} +5 -13
  118. package/dist/chunk-72VAXGEI.js.map +1 -0
  119. package/dist/{chunk-IKQTFQMI.js → chunk-755Q3DGF.js} +22 -3
  120. package/dist/chunk-755Q3DGF.js.map +1 -0
  121. package/dist/{chunk-D7FOKIY5.js → chunk-7AMFUATK.js} +12 -9
  122. package/dist/{chunk-D7FOKIY5.js.map → chunk-7AMFUATK.js.map} +1 -1
  123. package/dist/{chunk-BFVEEB7W.js → chunk-7ON2JW73.js} +57 -22
  124. package/dist/chunk-7ON2JW73.js.map +1 -0
  125. package/dist/chunk-7RNBVZZO.js +270 -0
  126. package/dist/chunk-7RNBVZZO.js.map +1 -0
  127. package/dist/{chunk-3FAMU5TX.js → chunk-A6USBZTO.js} +2 -2
  128. package/dist/{chunk-ZHIVCODT.js → chunk-A72SWTWW.js} +7 -7
  129. package/dist/{chunk-FC77VUX5.js → chunk-A7Q43EUZ.js} +2 -2
  130. package/dist/{chunk-UJBESW6X.js → chunk-AE7WMV4V.js} +7 -7
  131. package/dist/{chunk-J2HSAU72.js → chunk-AIK5W3IW.js} +29 -1
  132. package/dist/chunk-AIK5W3IW.js.map +1 -0
  133. package/dist/{chunk-A5UXZRRM.js → chunk-AJ7QCSIS.js} +2 -2
  134. package/dist/{chunk-FNMFJWBY.js → chunk-ANEUPF6O.js} +5 -5
  135. package/dist/chunk-AREGMX2J.js +50 -0
  136. package/dist/chunk-AREGMX2J.js.map +1 -0
  137. package/dist/chunk-AVPXXV6J.js +448 -0
  138. package/dist/chunk-AVPXXV6J.js.map +1 -0
  139. package/dist/{chunk-2A3OXAP2.js → chunk-B7RWBZXW.js} +2 -2
  140. package/dist/{chunk-QKQYXZVS.js → chunk-BI2OJF6J.js} +1819 -1481
  141. package/dist/chunk-BI2OJF6J.js.map +1 -0
  142. package/dist/{chunk-GWTFLF2P.js → chunk-BJMBJZ2Y.js} +59 -59
  143. package/dist/chunk-BJMBJZ2Y.js.map +1 -0
  144. package/dist/{chunk-74F5STS4.js → chunk-BLAKS3CP.js} +4 -4
  145. package/dist/{chunk-M7VGG3ID.js → chunk-BV5WMSIJ.js} +9 -9
  146. package/dist/{chunk-2BFLDH5F.js → chunk-BXLOS5AJ.js} +2 -2
  147. package/dist/{chunk-UXKMMNJD.js → chunk-C23BYTJV.js} +6 -6
  148. package/dist/{chunk-JZHHREBL.js → chunk-C3AKM55W.js} +2 -2
  149. package/dist/{chunk-RP7YXA67.js → chunk-C5JF6EZ7.js} +2 -2
  150. package/dist/{chunk-T7Y5KPLN.js → chunk-C6XBRYVD.js} +16 -15
  151. package/dist/chunk-C6XBRYVD.js.map +1 -0
  152. package/dist/{chunk-FTYDWKO6.js → chunk-CEN6DW6X.js} +12 -2
  153. package/dist/chunk-CEN6DW6X.js.map +1 -0
  154. package/dist/chunk-CGASUJVP.js +421 -0
  155. package/dist/chunk-CGASUJVP.js.map +1 -0
  156. package/dist/{chunk-EHCONI6E.js → chunk-CQMEM5CC.js} +2 -2
  157. package/dist/{chunk-PBNZVPFS.js → chunk-CXZQOVIE.js} +12 -12
  158. package/dist/chunk-D3V3UMET.js +44 -0
  159. package/dist/chunk-D3V3UMET.js.map +1 -0
  160. package/dist/{chunk-7N2RUBAP.js → chunk-DDA47TES.js} +2 -2
  161. package/dist/{chunk-64SE4X5G.js → chunk-DEBHXHJR.js} +3 -3
  162. package/dist/{chunk-EHK6KZHK.js → chunk-DNHVPZOD.js} +2 -2
  163. package/dist/{chunk-437S3G37.js → chunk-DQEMWVMT.js} +2 -2
  164. package/dist/{chunk-LZZNTPLR.js → chunk-DX3XRGLH.js} +2 -2
  165. package/dist/chunk-EA2H5LML.js +72 -0
  166. package/dist/chunk-EA2H5LML.js.map +1 -0
  167. package/dist/{chunk-INELTPRG.js → chunk-EEC7UJPX.js} +103 -106
  168. package/dist/chunk-EEC7UJPX.js.map +1 -0
  169. package/dist/{chunk-KSNVDPDF.js → chunk-EKBNDKDY.js} +21 -20
  170. package/dist/chunk-EKBNDKDY.js.map +1 -0
  171. package/dist/{chunk-YF6FMLBT.js → chunk-EQWEIIKQ.js} +64 -34
  172. package/dist/chunk-EQWEIIKQ.js.map +1 -0
  173. package/dist/{chunk-DZKHSDOM.js → chunk-ES36HTSC.js} +4 -4
  174. package/dist/{chunk-TN7THW5U.js → chunk-ET356KSB.js} +2 -2
  175. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  176. package/dist/{chunk-DMSDUGDM.js → chunk-F7GAIWFA.js} +20 -20
  177. package/dist/{chunk-G6XXLKC5.js → chunk-FFPJINJE.js} +6 -6
  178. package/dist/chunk-FMLKOL2Z.js +636 -0
  179. package/dist/chunk-FMLKOL2Z.js.map +1 -0
  180. package/dist/chunk-FQC3DHH2.js +20 -0
  181. package/dist/chunk-FQC3DHH2.js.map +1 -0
  182. package/dist/{chunk-G4CN3ZXL.js → chunk-FUTF72MJ.js} +15 -15
  183. package/dist/chunk-GCASGYIO.js +71 -0
  184. package/dist/chunk-GCASGYIO.js.map +1 -0
  185. package/dist/{chunk-HF77M34J.js → chunk-GFPZS3F5.js} +8 -8
  186. package/dist/{chunk-BCDKBDSY.js → chunk-GNVZQ5JN.js} +99 -41
  187. package/dist/chunk-GNVZQ5JN.js.map +1 -0
  188. package/dist/{chunk-G6NKALRN.js → chunk-H4WG3UQZ.js} +2 -2
  189. package/dist/{chunk-UDQUSU27.js → chunk-HNZKVLAN.js} +2 -2
  190. package/dist/{chunk-5EZXHU36.js → chunk-HYQJQGD3.js} +2 -2
  191. package/dist/{chunk-PLQE6MJA.js → chunk-IFH6LWTO.js} +50 -8
  192. package/dist/chunk-IFH6LWTO.js.map +1 -0
  193. package/dist/{chunk-RULE4VG5.js → chunk-ILGC6JH6.js} +21 -6
  194. package/dist/chunk-ILGC6JH6.js.map +1 -0
  195. package/dist/chunk-IUM3VSHI.js +113 -0
  196. package/dist/chunk-IUM3VSHI.js.map +1 -0
  197. package/dist/{chunk-TRWPCR6P.js → chunk-JECBQBEX.js} +3 -3
  198. package/dist/{chunk-E6CBU7XP.js → chunk-JYIFGGEM.js} +2 -2
  199. package/dist/{chunk-VA4WSW54.js → chunk-JYIJNNN3.js} +3 -3
  200. package/dist/{chunk-L5HJQC36.js → chunk-K2TDK7GG.js} +3 -3
  201. package/dist/{chunk-KDXVOFYY.js → chunk-KAVBJSBQ.js} +4 -4
  202. package/dist/{chunk-BS4SGLWV.js → chunk-KF5EUVKQ.js} +16 -6
  203. package/dist/chunk-KF5EUVKQ.js.map +1 -0
  204. package/dist/chunk-KFZY2OFH.js +42 -0
  205. package/dist/chunk-KFZY2OFH.js.map +1 -0
  206. package/dist/{chunk-4OKLXWDZ.js → chunk-KNB7IBBU.js} +2 -2
  207. package/dist/{chunk-QLULYKWZ.js → chunk-KSXJOC4F.js} +2 -2
  208. package/dist/{chunk-2S6QHZGJ.js → chunk-L2DPF4DF.js} +1 -1
  209. package/dist/{chunk-JI5AVXYO.js → chunk-LEPOP46W.js} +6 -6
  210. package/dist/{chunk-O7BBFJLP.js → chunk-LTAVSOZW.js} +2 -2
  211. package/dist/{chunk-E3VODCC3.js → chunk-LUDXM7MD.js} +14 -2
  212. package/dist/{chunk-E3VODCC3.js.map → chunk-LUDXM7MD.js.map} +1 -1
  213. package/dist/{chunk-2IZAGAJR.js → chunk-LULR4IUW.js} +3 -3
  214. package/dist/{chunk-QQGPUKMT.js → chunk-MC5Q3J7S.js} +398 -7
  215. package/dist/chunk-MC5Q3J7S.js.map +1 -0
  216. package/dist/{chunk-WMWVO45V.js → chunk-MC6XO7T4.js} +2 -2
  217. package/dist/chunk-MKQBXQKU.js +107 -0
  218. package/dist/chunk-MKQBXQKU.js.map +1 -0
  219. package/dist/{chunk-7PND4PUC.js → chunk-MMGGVZ4Q.js} +29 -9
  220. package/dist/{chunk-7PND4PUC.js.map → chunk-MMGGVZ4Q.js.map} +1 -1
  221. package/dist/{chunk-D6RIFG4O.js → chunk-MNB5RMAY.js} +2 -2
  222. package/dist/{chunk-Q5WG43OP.js → chunk-MYFQ3ED4.js} +7 -7
  223. package/dist/{chunk-V3UE5I5F.js → chunk-MYVPLJOO.js} +2 -2
  224. package/dist/chunk-NINSR6LX.js +83 -0
  225. package/dist/chunk-NINSR6LX.js.map +1 -0
  226. package/dist/{chunk-MTWKDOVO.js → chunk-NLE6TSLG.js} +2 -2
  227. package/dist/{chunk-OAZVR2BV.js → chunk-OGCAYLNA.js} +8 -6
  228. package/dist/{chunk-OAZVR2BV.js.map → chunk-OGCAYLNA.js.map} +1 -1
  229. package/dist/{chunk-DBRYEIGY.js → chunk-OJP3N7UK.js} +2 -2
  230. package/dist/{chunk-VDW4VU7B.js → chunk-OQJIB32H.js} +28 -23
  231. package/dist/chunk-OQJIB32H.js.map +1 -0
  232. package/dist/{chunk-SMX5267P.js → chunk-OZKOPQCM.js} +2 -2
  233. package/dist/{chunk-JCK7V6SN.js → chunk-P65YJEEL.js} +2 -2
  234. package/dist/{chunk-MTR7BZQN.js → chunk-PBGWYM3H.js} +3 -3
  235. package/dist/chunk-PIG4WJFQ.js +87 -0
  236. package/dist/chunk-PIG4WJFQ.js.map +1 -0
  237. package/dist/chunk-PJSK6OJM.js +201 -0
  238. package/dist/chunk-PJSK6OJM.js.map +1 -0
  239. package/dist/{chunk-OVJ2HIPE.js → chunk-PMUD7ERF.js} +158 -125
  240. package/dist/chunk-PMUD7ERF.js.map +1 -0
  241. package/dist/chunk-PQVCH2AE.js +76 -0
  242. package/dist/chunk-PQVCH2AE.js.map +1 -0
  243. package/dist/{chunk-AXTXN4T6.js → chunk-PWFCHOB3.js} +2 -2
  244. package/dist/{chunk-FDPZVY6D.js → chunk-QFN4U7AG.js} +2 -2
  245. package/dist/{chunk-K2ZLHESQ.js → chunk-QIEUU4UK.js} +2 -2
  246. package/dist/{chunk-DYP3YYJJ.js → chunk-QJAQNXWI.js} +39 -5
  247. package/dist/chunk-QJAQNXWI.js.map +1 -0
  248. package/dist/{chunk-3D6AFGQR.js → chunk-QVT3E4CE.js} +7 -7
  249. package/dist/{chunk-FIJUY7GG.js → chunk-R26ZMXR2.js} +23 -23
  250. package/dist/{chunk-FIJUY7GG.js.map → chunk-R26ZMXR2.js.map} +1 -1
  251. package/dist/chunk-R3DJVOSM.js +34 -0
  252. package/dist/chunk-R3DJVOSM.js.map +1 -0
  253. package/dist/{chunk-SFQ6QNL7.js → chunk-R6YJI3HE.js} +2 -1
  254. package/dist/{chunk-5CJVLG3H.js → chunk-RFDD3B7W.js} +5 -5
  255. package/dist/{chunk-JBPKEARU.js → chunk-RKNJBZ55.js} +4 -4
  256. package/dist/chunk-ROI7BK7H.js +88 -0
  257. package/dist/chunk-ROI7BK7H.js.map +1 -0
  258. package/dist/{chunk-FNZQGN76.js → chunk-S2BBISHU.js} +4 -4
  259. package/dist/{chunk-IALLX7EE.js → chunk-S2FSBU4H.js} +3 -3
  260. package/dist/{chunk-BFPETA3S.js → chunk-SCCO46KZ.js} +3 -2
  261. package/dist/chunk-SCCO46KZ.js.map +1 -0
  262. package/dist/chunk-SK7AMF2J.js +168 -0
  263. package/dist/chunk-SK7AMF2J.js.map +1 -0
  264. package/dist/{chunk-KSBEEMQF.js → chunk-TG6IHVE2.js} +151 -6
  265. package/dist/chunk-TG6IHVE2.js.map +1 -0
  266. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  267. package/dist/{chunk-DPAENM7O.js → chunk-U3ZAVRP3.js} +2 -2
  268. package/dist/{chunk-ODPLEWB6.js → chunk-UFGFMWAD.js} +47 -17
  269. package/dist/chunk-UFGFMWAD.js.map +1 -0
  270. package/dist/{chunk-4KJLYTLS.js → chunk-V57NLO5P.js} +2 -2
  271. package/dist/{chunk-FLVL4L3L.js → chunk-VDX2J7OX.js} +2 -2
  272. package/dist/{chunk-AJLLIIML.js → chunk-W4FBGYGK.js} +1 -1
  273. package/dist/{chunk-TXLGHDHS.js → chunk-W4XJFLAL.js} +53 -32
  274. package/dist/chunk-W4XJFLAL.js.map +1 -0
  275. package/dist/{chunk-6SXVCD7W.js → chunk-W7IGLX4G.js} +2 -2
  276. package/dist/{chunk-KQDNAXOM.js → chunk-WG5HSQ4P.js} +19 -6
  277. package/dist/chunk-WG5HSQ4P.js.map +1 -0
  278. package/dist/{chunk-LTJAMRGI.js → chunk-WL5EBRDH.js} +17 -3
  279. package/dist/chunk-WL5EBRDH.js.map +1 -0
  280. package/dist/{chunk-KAWTOYAW.js → chunk-WLZYGLJ4.js} +2 -2
  281. package/dist/{chunk-RO4LQGT3.js → chunk-WON4APQY.js} +4 -4
  282. package/dist/{chunk-PQVK2S3J.js → chunk-WZFAPHIK.js} +6 -6
  283. package/dist/{chunk-KQPJLK3X.js → chunk-XL24QQ63.js} +2 -2
  284. package/dist/{chunk-LZOV7MX7.js → chunk-XQDRKZ7V.js} +6476 -5747
  285. package/dist/chunk-XQDRKZ7V.js.map +1 -0
  286. package/dist/{chunk-RVZRYUQH.js → chunk-YROFNEG5.js} +7 -7
  287. package/dist/{chunk-RAR2BSEM.js → chunk-YV32N67O.js} +1 -1
  288. package/dist/chunk-YV32N67O.js.map +1 -0
  289. package/dist/{chunk-PBSAPHHA.js → chunk-YVKF3HEW.js} +1 -1
  290. package/dist/chunk-YVKF3HEW.js.map +1 -0
  291. package/dist/{chunk-VRAD2FGO.js → chunk-Z54EM5YZ.js} +12 -12
  292. package/dist/{chunk-2VN4JAX7.js → chunk-ZOKZIR5K.js} +95 -33
  293. package/dist/chunk-ZOKZIR5K.js.map +1 -0
  294. package/dist/{chunk-DCFV7A7H.js → chunk-ZVAYK7GU.js} +101 -91
  295. package/dist/chunk-ZVAYK7GU.js.map +1 -0
  296. package/dist/{cli-Cd5fMmhq.d.ts → cli-5tNLVTvJ.d.ts} +28 -12
  297. package/dist/cli.d.ts +16 -9
  298. package/dist/cli.js +142 -127
  299. package/dist/compounding/engine.d.ts +5 -2
  300. package/dist/compounding/engine.js +36 -26
  301. package/dist/compounding/preference-consolidator.d.ts +3 -1
  302. package/dist/compounding/preference-consolidator.js +1 -1
  303. package/dist/compression-optimizer.d.ts +3 -1
  304. package/dist/config-qmd.d.ts +8 -0
  305. package/dist/config-qmd.js +20 -0
  306. package/dist/config-qmd.js.map +1 -0
  307. package/dist/config.d.ts +3 -1
  308. package/dist/config.js +15 -10
  309. package/dist/connectors/codex-materialize-runner.d.ts +3 -1
  310. package/dist/connectors/codex-materialize-runner.js +36 -26
  311. package/dist/connectors/codex-materialize.d.ts +3 -1
  312. package/dist/connectors/codex-materialize.js +2 -2
  313. package/dist/connectors/index.d.ts +3 -1
  314. package/dist/connectors/index.js +41 -32
  315. package/dist/consolidation-provenance-check.d.ts +5 -2
  316. package/dist/consolidation-undo.d.ts +5 -2
  317. package/dist/contradiction/index.d.ts +5 -2
  318. package/dist/contradiction/index.js +10 -4
  319. package/dist/{contradiction-scan-J57T5QV7.js → contradiction-scan-RJBABNRH.js} +4 -4
  320. package/dist/contradiction-scan-RJBABNRH.js.map +1 -0
  321. package/dist/conversation-index/backend.d.ts +3 -1
  322. package/dist/conversation-index/backend.js +5 -5
  323. package/dist/conversation-index/chunker.d.ts +3 -1
  324. package/dist/conversation-index/cleanup.js +2 -2
  325. package/dist/conversation-index/faiss-adapter.d.ts +3 -1
  326. package/dist/conversation-index/faiss-adapter.js +2 -2
  327. package/dist/conversation-index/indexer.d.ts +3 -1
  328. package/dist/conversation-index/indexer.js +2 -2
  329. package/dist/conversation-index/search.d.ts +3 -1
  330. package/dist/conversation-index/search.js +2 -2
  331. package/dist/cross-namespace-budget.d.ts +45 -1
  332. package/dist/cross-namespace-budget.js +5 -3
  333. package/dist/dashboard-runtime.js +2 -2
  334. package/dist/day-summary.d.ts +3 -1
  335. package/dist/day-summary.js +3 -3
  336. package/dist/delinearize.d.ts +3 -1
  337. package/dist/direct-answer-wiring.d.ts +3 -1
  338. package/dist/direct-answer.d.ts +3 -1
  339. package/dist/{dreams-ledger-6UODQOX6.js → dreams-ledger-567VKVOU.js} +4 -2
  340. package/dist/dreams-ledger-567VKVOU.js.map +1 -0
  341. package/dist/embedding-fallback.d.ts +3 -1
  342. package/dist/embedding-fallback.js +3 -3
  343. package/dist/enrichment/index.d.ts +3 -1
  344. package/dist/entity-retrieval.d.ts +5 -2
  345. package/dist/entity-retrieval.js +35 -26
  346. package/dist/entity-schema.d.ts +3 -1
  347. package/dist/explicit-capture.d.ts +13 -8
  348. package/dist/explicit-capture.js +7 -3
  349. package/dist/extraction-faithfulness.d.ts +3 -1
  350. package/dist/extraction-faithfulness.js +5 -5
  351. package/dist/extraction-judge-telemetry.d.ts +3 -1
  352. package/dist/extraction-judge-telemetry.js +2 -2
  353. package/dist/extraction-judge-training.d.ts +3 -1
  354. package/dist/extraction-judge-training.js +2 -2
  355. package/dist/extraction-judge.d.ts +3 -1
  356. package/dist/extraction-judge.js +5 -5
  357. package/dist/extraction.d.ts +3 -1
  358. package/dist/extraction.js +13 -12
  359. package/dist/faithfulness-config.js +3 -2
  360. package/dist/fallback-llm.d.ts +3 -1
  361. package/dist/fallback-llm.js +4 -4
  362. package/dist/{first-start-migration-TI67YMLG.js → first-start-migration-CSRHCJIQ.js} +2 -2
  363. package/dist/{forget-CQCLB5LB.js → forget-HRPLVB3W.js} +5 -5
  364. package/dist/graph-dashboard-diff.d.ts +3 -1
  365. package/dist/graph-dashboard-key.d.ts +3 -1
  366. package/dist/graph-dashboard-parser.d.ts +3 -1
  367. package/dist/{graph-edge-decay-IOGBBAGC.js → graph-edge-decay-Z6ENALU2.js} +3 -3
  368. package/dist/graph-edge-decay-Z6ENALU2.js.map +1 -0
  369. package/dist/graph-edge-reinforcement.d.ts +3 -1
  370. package/dist/graph-snapshot.d.ts +3 -1
  371. package/dist/graph-snapshot.js +2 -2
  372. package/dist/graph.d.ts +3 -1
  373. package/dist/graph.js +1 -1
  374. package/dist/identity-continuity.d.ts +1 -2
  375. package/dist/importance.d.ts +3 -1
  376. package/dist/importers/index.d.ts +3 -1
  377. package/dist/in-flight-reads.d.ts +3 -1
  378. package/dist/index.d.ts +473 -464
  379. package/dist/index.js +287 -239
  380. package/dist/intent.d.ts +3 -1
  381. package/dist/lcm/archive.js +2 -2
  382. package/dist/lcm/engine.d.ts +3 -1
  383. package/dist/lcm/engine.js +6 -6
  384. package/dist/lcm/index.d.ts +3 -1
  385. package/dist/lcm/index.js +13 -13
  386. package/dist/lcm/schema.js +2 -2
  387. package/dist/lcm/summarizer.js +3 -3
  388. package/dist/lcm/tools.d.ts +3 -1
  389. package/dist/lifecycle.d.ts +3 -1
  390. package/dist/live-connectors-runner.d.ts +3 -1
  391. package/dist/local-llm.d.ts +5 -2
  392. package/dist/local-llm.js +3 -3
  393. package/dist/local-model-endpoint.d.ts +3 -1
  394. package/dist/logger.d.ts +3 -1
  395. package/dist/logger.js +7 -3
  396. package/dist/maintenance/archive-observations.js +2 -2
  397. package/dist/maintenance/memory-governance.d.ts +3 -1
  398. package/dist/maintenance/memory-governance.js +35 -25
  399. package/dist/maintenance/migrate-observations.js +3 -3
  400. package/dist/maintenance/observation-ledger-utils.js +2 -2
  401. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +102 -1
  402. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +37 -25
  403. package/dist/maintenance/rebuild-memory-projection.js +38 -28
  404. package/dist/maintenance/rebuild-observations.js +3 -3
  405. package/dist/mcp-memory-inspector-app.d.ts +22 -15
  406. package/dist/memory-action-policy.d.ts +3 -1
  407. package/dist/memory-cache.d.ts +3 -1
  408. package/dist/memory-lifecycle-ledger-utils.d.ts +25 -2
  409. package/dist/memory-lifecycle-ledger-utils.js +11 -1
  410. package/dist/memory-projection-store.d.ts +3 -1
  411. package/dist/memory-projection-store.js +2 -2
  412. package/dist/memory-provenance.d.ts +3 -1
  413. package/dist/memory-worth-outcomes.d.ts +5 -2
  414. package/dist/model-registry.js +2 -2
  415. package/dist/models-json.d.ts +3 -1
  416. package/dist/models-json.js +2 -2
  417. package/dist/namespaces/migrate.d.ts +6 -3
  418. package/dist/namespaces/migrate.js +51 -42
  419. package/dist/namespaces/principal.d.ts +27 -2
  420. package/dist/namespaces/principal.js +6 -2
  421. package/dist/namespaces/search.d.ts +16 -2
  422. package/dist/namespaces/search.js +24 -20
  423. package/dist/namespaces/storage.d.ts +24 -3
  424. package/dist/namespaces/storage.js +35 -27
  425. package/dist/native-knowledge.d.ts +3 -1
  426. package/dist/native-knowledge.js +2 -2
  427. package/dist/negative.js +2 -2
  428. package/dist/offline-sync-exclude-globs.d.ts +13 -0
  429. package/dist/offline-sync-exclude-globs.js +10 -0
  430. package/dist/offline-sync-exclude-globs.js.map +1 -0
  431. package/dist/offline-sync-impression-drain.d.ts +78 -0
  432. package/dist/offline-sync-impression-drain.js +37 -0
  433. package/dist/offline-sync-impression-drain.js.map +1 -0
  434. package/dist/offline-sync.js +4 -2
  435. package/dist/operator-toolkit.d.ts +5 -2
  436. package/dist/operator-toolkit.js +69 -54
  437. package/dist/orchestration/compression-guideline-coordinator.d.ts +5 -2
  438. package/dist/orchestration/compression-guideline-coordinator.js +3 -3
  439. package/dist/orchestration/maintenance.d.ts +121 -2
  440. package/dist/orchestration/maintenance.js +44 -31
  441. package/dist/{orchestrator-Bp_jeBHK.d.ts → orchestrator-CQJ8U7En.d.ts} +85 -79
  442. package/dist/orchestrator.d.ts +13 -8
  443. package/dist/orchestrator.js +194 -177
  444. package/dist/patterns-cli.d.ts +3 -1
  445. package/dist/{pipeline-BAj5bx7g.d.ts → pipeline-Cn-kvYeV.d.ts} +1 -1
  446. package/dist/policy-runtime.d.ts +3 -1
  447. package/dist/prioritized-embed.d.ts +19 -0
  448. package/dist/prioritized-embed.js +8 -0
  449. package/dist/prioritized-embed.js.map +1 -0
  450. package/dist/proactive-contention.d.ts +28 -0
  451. package/dist/proactive-contention.js +8 -0
  452. package/dist/proactive-contention.js.map +1 -0
  453. package/dist/profiling.js +2 -2
  454. package/dist/provenance.d.ts +3 -1
  455. package/dist/provenance.js +3 -2
  456. package/dist/{purge-NDWV6WYD.js → purge-IRF47ZTX.js} +3 -3
  457. package/dist/{qmd-_MDSlj1m.d.ts → qmd-oh9VXz1d.d.ts} +11 -1
  458. package/dist/qmd-preflight.d.ts +5 -2
  459. package/dist/qmd-recall-cache.d.ts +3 -1
  460. package/dist/qmd-status.d.ts +57 -0
  461. package/dist/qmd-status.js +18 -0
  462. package/dist/qmd-status.js.map +1 -0
  463. package/dist/qmd.d.ts +5 -2
  464. package/dist/qmd.js +7 -3
  465. package/dist/recall-concurrency-config.d.ts +16 -0
  466. package/dist/recall-concurrency-config.js +10 -0
  467. package/dist/recall-concurrency-config.js.map +1 -0
  468. package/dist/recall-disclosure-escalation.d.ts +3 -1
  469. package/dist/recall-explain-renderer.d.ts +4 -1
  470. package/dist/recall-explain-renderer.js +3 -3
  471. package/dist/recall-memory-map.d.ts +26 -0
  472. package/dist/recall-memory-map.js +18 -0
  473. package/dist/recall-memory-map.js.map +1 -0
  474. package/dist/recall-planner-llm.d.ts +3 -1
  475. package/dist/recall-planner-llm.js +4 -4
  476. package/dist/recall-qos.js +2 -2
  477. package/dist/recall-state.d.ts +163 -2
  478. package/dist/recall-state.js +6 -3
  479. package/dist/recall-tag-filter.d.ts +3 -1
  480. package/dist/recall-timings.d.ts +11 -2
  481. package/dist/recall-timings.js +3 -1
  482. package/dist/recall-xray-cli.d.ts +3 -1
  483. package/dist/recall-xray-cli.js +4 -4
  484. package/dist/recall-xray-renderer.d.ts +3 -1
  485. package/dist/recall-xray-renderer.js +3 -3
  486. package/dist/recall-xray.d.ts +3 -1
  487. package/dist/recall-xray.js +2 -2
  488. package/dist/relevance.js +3 -3
  489. package/dist/replay/normalizers/chatgpt.d.ts +3 -1
  490. package/dist/replay/normalizers/claude.d.ts +3 -1
  491. package/dist/replay/normalizers/openclaw.d.ts +3 -1
  492. package/dist/replay/normalizers/shared.d.ts +3 -1
  493. package/dist/replay/runner.d.ts +3 -1
  494. package/dist/replay/types.d.ts +3 -1
  495. package/dist/rerank.d.ts +8 -1
  496. package/dist/rerank.js +3 -1
  497. package/dist/resolution-3K27T4VO.js +18 -0
  498. package/dist/resolution-3K27T4VO.js.map +1 -0
  499. package/dist/resolve-auth-token.d.ts +3 -1
  500. package/dist/resolve-provider-secret.js +2 -2
  501. package/dist/resume-bundles.js +19 -14
  502. package/dist/retrieval-agents.d.ts +19 -16
  503. package/dist/retrieval-agents.js +3 -3
  504. package/dist/retrieval-tiers.d.ts +3 -1
  505. package/dist/routing/engine.d.ts +3 -1
  506. package/dist/routing/store.d.ts +3 -1
  507. package/dist/routing/store.js +2 -2
  508. package/dist/salvage-envelope.d.ts +18 -0
  509. package/dist/salvage-envelope.js +15 -0
  510. package/dist/salvage-envelope.js.map +1 -0
  511. package/dist/schemas.d.ts +98 -98
  512. package/dist/scope-profiles-EpJG7yKP.d.ts +30 -0
  513. package/dist/search/document-scanner.js +3 -2
  514. package/dist/search/embed-helper.d.ts +3 -1
  515. package/dist/search/embed-helper.js +3 -3
  516. package/dist/search/factory.d.ts +3 -1
  517. package/dist/search/factory.js +18 -16
  518. package/dist/search/index.d.ts +3 -1
  519. package/dist/search/index.js +22 -20
  520. package/dist/search/lancedb-backend.d.ts +3 -1
  521. package/dist/search/lancedb-backend.js +6 -5
  522. package/dist/search/meilisearch-backend.d.ts +3 -1
  523. package/dist/search/meilisearch-backend.js +6 -5
  524. package/dist/search/noop-backend.d.ts +3 -1
  525. package/dist/search/orama-backend.d.ts +3 -1
  526. package/dist/search/orama-backend.js +6 -5
  527. package/dist/search/port.d.ts +16 -2
  528. package/dist/search/port.js +1 -1
  529. package/dist/search/remote-backend.d.ts +3 -1
  530. package/dist/search/remote-backend.js +3 -3
  531. package/dist/secure-store/index.d.ts +12 -1
  532. package/dist/secure-store/index.js +6 -3
  533. package/dist/{semantic-consolidation-C2m5eHHE.d.ts → semantic-consolidation-DILZEQq3.d.ts} +1 -1
  534. package/dist/semantic-consolidation.d.ts +4 -2
  535. package/dist/semantic-consolidation.js +38 -29
  536. package/dist/semantic-rule-promotion.js +34 -24
  537. package/dist/semantic-rule-verifier.d.ts +3 -1
  538. package/dist/semantic-rule-verifier.js +35 -25
  539. package/dist/session-observer-bands.d.ts +3 -1
  540. package/dist/session-observer-state.d.ts +3 -1
  541. package/dist/session-observer-state.js +2 -2
  542. package/dist/session-transcript-migration.js +2 -2
  543. package/dist/shared-context/manager.d.ts +11 -9
  544. package/dist/shared-context/manager.js +3 -3
  545. package/dist/signal.d.ts +3 -1
  546. package/dist/{storage-hr_Y2z5R.d.ts → storage-NIyKYD8g.d.ts} +489 -110
  547. package/dist/storage-guards.js +2 -2
  548. package/dist/storage.d.ts +6 -3
  549. package/dist/storage.js +38 -26
  550. package/dist/structured-attributes.d.ts +23 -1
  551. package/dist/structured-attributes.js +7 -1
  552. package/dist/summarizer.d.ts +3 -1
  553. package/dist/summarizer.js +11 -11
  554. package/dist/summary-snapshot.d.ts +3 -1
  555. package/dist/summary-snapshot.js +2 -2
  556. package/dist/temporal-index-batch.d.ts +1 -0
  557. package/dist/temporal-index-batch.js +10 -0
  558. package/dist/temporal-index-batch.js.map +1 -0
  559. package/dist/temporal-index-compat.d.ts +279 -1
  560. package/dist/temporal-index-compat.js +1 -1
  561. package/dist/temporal-index.d.ts +1 -225
  562. package/dist/temporal-index.js +35 -3
  563. package/dist/temporal-supersession.d.ts +5 -2
  564. package/dist/temporal-supersession.js +2 -2
  565. package/dist/temporal-timeline-recall.d.ts +3 -1
  566. package/dist/temporal-validity.d.ts +3 -1
  567. package/dist/threading.d.ts +3 -1
  568. package/dist/threading.js +2 -2
  569. package/dist/tier-migration.d.ts +5 -2
  570. package/dist/tier-routing.d.ts +3 -1
  571. package/dist/{tier-stats-KV7IWCJR.js → tier-stats-QEJSPFV2.js} +2 -2
  572. package/dist/tokens.js +3 -3
  573. package/dist/topics.d.ts +3 -1
  574. package/dist/transcript.d.ts +3 -1
  575. package/dist/transcript.js +2 -2
  576. package/dist/transfer/backup.js +3 -3
  577. package/dist/transfer/capsule-export.js +3 -3
  578. package/dist/transfer/capsule-import.js +3 -3
  579. package/dist/transfer/types.d.ts +90 -90
  580. package/dist/trust-score-stage.d.ts +75 -38
  581. package/dist/trust-score-stage.js +11 -3
  582. package/dist/trust-score.d.ts +3 -1
  583. package/dist/{types-Cg8q7W7_.d.ts → types-D_fG2ix8.d.ts} +39 -63
  584. package/dist/types-continuity.d.ts +52 -0
  585. package/dist/types-continuity.js +1 -0
  586. package/dist/types-continuity.js.map +1 -0
  587. package/dist/types.d.ts +3 -1
  588. package/dist/types.js +1 -1
  589. package/dist/utility-runtime.d.ts +3 -1
  590. package/dist/utils/serialize-mutations.d.ts +31 -2
  591. package/dist/utils/serialize-mutations.js +1 -1
  592. package/dist/verified-recall.js +36 -26
  593. package/dist/write-envelope.d.ts +282 -0
  594. package/dist/write-envelope.js +35 -0
  595. package/dist/write-envelope.js.map +1 -0
  596. package/dist/write-quarantine.d.ts +94 -0
  597. package/dist/write-quarantine.js +11 -0
  598. package/dist/write-quarantine.js.map +1 -0
  599. package/dist/write-rate-limiter.d.ts +45 -0
  600. package/dist/write-rate-limiter.js +9 -0
  601. package/dist/write-rate-limiter.js.map +1 -0
  602. package/package.json +37 -2
  603. package/src/access-admin-ops-surface.ts +17 -7
  604. package/src/access-boundary.test.ts +28 -0
  605. package/src/access-boundary.ts +31 -2
  606. package/src/access-citation.test.ts +72 -0
  607. package/src/access-citation.ts +139 -0
  608. package/src/access-errors.ts +21 -0
  609. package/src/access-fingerprint-parity.test.ts +211 -0
  610. package/src/access-http-cancellation.test.ts +59 -21
  611. package/src/access-http.test.ts +90 -0
  612. package/src/access-http.ts +79 -79
  613. package/src/access-mcp-cancellation.test.ts +16 -7
  614. package/src/access-memory-search-fanout.test.ts +220 -0
  615. package/src/access-memory-search-fanout.ts +168 -0
  616. package/src/access-observe-quarantine.test.ts +172 -0
  617. package/src/access-observe-write-surface.ts +137 -11
  618. package/src/access-operations-batch.ts +2 -0
  619. package/src/access-recall-concurrency.ts +773 -0
  620. package/src/access-recall-response.ts +307 -0
  621. package/src/access-recall-surface.ts +100 -252
  622. package/src/access-service-coding-write.test.ts +22 -4
  623. package/src/access-service-health.test.ts +49 -0
  624. package/src/access-service-namespace.test.ts +486 -8
  625. package/src/access-service-observe-scope.test.ts +4 -0
  626. package/src/access-service-recall-concurrency.test.ts +1990 -0
  627. package/src/access-service.ts +186 -179
  628. package/src/access-surface-catalog.ts +1 -0
  629. package/src/access-token-capabilities.ts +16 -9
  630. package/src/active-recall.test.ts +35 -0
  631. package/src/active-recall.ts +7 -0
  632. package/src/bounded-jsonl-state.ts +93 -0
  633. package/src/buffer-session.test.ts +422 -5
  634. package/src/buffer-surprise-trigger.test.ts +41 -0
  635. package/src/buffer.ts +218 -3
  636. package/src/capabilities-cluster-b.test.ts +1 -0
  637. package/src/capabilities.ts +5 -6
  638. package/src/cli.ts +21 -22
  639. package/src/coding/architecture-surfaces.test.ts +6 -3
  640. package/src/coding/architecture-surfaces.ts +36 -25
  641. package/src/coding/decision-surfaces.ts +42 -34
  642. package/src/compounding/engine.test.ts +5 -1
  643. package/src/compounding/engine.ts +7 -7
  644. package/src/config-qmd.ts +89 -0
  645. package/src/config.test.ts +136 -0
  646. package/src/config.ts +113 -118
  647. package/src/connector-provenance-lifecycle.test.ts +4 -4
  648. package/src/connectors/coerce.test.ts +131 -0
  649. package/src/connectors/coerce.ts +80 -7
  650. package/src/contradiction/contradiction.test.ts +10 -6
  651. package/src/contradiction/resolution.ts +17 -4
  652. package/src/correction/correction-access-wiring.ts +62 -18
  653. package/src/cross-namespace-budget.test.ts +35 -0
  654. package/src/cross-namespace-budget.ts +86 -19
  655. package/src/explicit-capture.ts +54 -17
  656. package/src/extraction.ts +2 -3
  657. package/src/faithfulness-graph-guard.test.ts +16 -69
  658. package/src/graph-recall.test.ts +31 -0
  659. package/src/index.ts +22 -2
  660. package/src/local-llm.ts +4 -6
  661. package/src/logger.ts +16 -0
  662. package/src/maintenance/dreams-ledger.ts +100 -49
  663. package/src/maintenance/rebuild-memory-lifecycle-ledger-cli.ts +77 -0
  664. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +373 -9
  665. package/src/memory-lifecycle-ledger-utils.ts +51 -5
  666. package/src/namespaces/principal.test.ts +37 -1
  667. package/src/namespaces/principal.ts +48 -0
  668. package/src/namespaces/search.test.ts +79 -5
  669. package/src/namespaces/search.ts +59 -6
  670. package/src/namespaces/storage.ts +43 -1
  671. package/src/offline-sync-exclude-globs.ts +58 -0
  672. package/src/offline-sync-impression-drain.test.ts +316 -0
  673. package/src/offline-sync-impression-drain.ts +260 -0
  674. package/src/offline-sync.test.ts +32 -5
  675. package/src/offline-sync.ts +1 -14
  676. package/src/operator-toolkit.ts +1 -1
  677. package/src/orchestration/batch-deindex-flush.test.ts +146 -0
  678. package/src/orchestration/consolidation-run.ts +96 -85
  679. package/src/orchestration/extraction-envelope.ts +94 -0
  680. package/src/orchestration/extraction-persist-promotion.ts +134 -0
  681. package/src/orchestration/extraction-persist.ts +325 -260
  682. package/src/orchestration/extraction-run.test.ts +1 -1
  683. package/src/orchestration/extraction-run.ts +2 -2
  684. package/src/orchestration/graph-recall-coordinator.ts +18 -7
  685. package/src/orchestration/lifecycle-policy-coordinator.ts +63 -48
  686. package/src/orchestration/maintenance.test.ts +2103 -3
  687. package/src/orchestration/maintenance.ts +574 -2
  688. package/src/orchestration/namespace-read-fanout.ts +14 -8
  689. package/src/orchestration/orchestrator-helpers.ts +8 -1
  690. package/src/orchestration/orchestrator-init.ts +22 -6
  691. package/src/orchestration/orchestrator-namespace-scope.ts +71 -0
  692. package/src/orchestration/persistence-index.ts +64 -9
  693. package/src/orchestration/qmd-result-resolver.ts +27 -0
  694. package/src/orchestration/recall-entry.ts +34 -19
  695. package/src/orchestration/recall-internal.ts +207 -79
  696. package/src/orchestration/recall-rerank-coordinator.ts +229 -82
  697. package/src/orchestration/recall-result-formatter.ts +76 -20
  698. package/src/orchestration/recall-search-pipeline.test.ts +70 -0
  699. package/src/orchestration/recall-search-pipeline.ts +102 -129
  700. package/src/orchestration/recall-section-coordinator.ts +315 -85
  701. package/src/orchestration/semantic-consolidation-coordinator.ts +69 -37
  702. package/src/orchestration/session-context.ts +21 -0
  703. package/src/orchestration/workspace-ops.test.ts +348 -0
  704. package/src/orchestration/workspace-ops.ts +168 -34
  705. package/src/orchestrator-chunked-hash-durability.test.ts +126 -0
  706. package/src/orchestrator-connector-dedup.test.ts +1061 -40
  707. package/src/orchestrator-flush.test.ts +155 -39
  708. package/src/orchestrator-nonchunked-hash-durability.test.ts +121 -0
  709. package/src/orchestrator-source-attribution.test.ts +9 -9
  710. package/src/orchestrator.ts +160 -149
  711. package/src/prioritized-embed.ts +123 -0
  712. package/src/proactive-contention.test.ts +80 -0
  713. package/src/proactive-contention.ts +39 -0
  714. package/src/procedural/procedure-miner.test.ts +19 -14
  715. package/src/procedural/procedure-miner.ts +44 -13
  716. package/src/qmd-client.test.ts +111 -1
  717. package/src/qmd-status.ts +186 -0
  718. package/src/qmd.ts +47 -47
  719. package/src/recall-budget-config.test.ts +30 -0
  720. package/src/recall-cold-deadline.test.ts +149 -0
  721. package/src/recall-concurrency-config.ts +28 -0
  722. package/src/recall-memory-map.ts +74 -0
  723. package/src/recall-rerank-coordinator.test.ts +508 -0
  724. package/src/recall-state.test.ts +825 -1
  725. package/src/recall-state.ts +406 -7
  726. package/src/recall-timings-access.test.ts +41 -0
  727. package/src/recall-timings.ts +16 -0
  728. package/src/relay/mission-access.test.ts +5 -5
  729. package/src/rerank.ts +22 -0
  730. package/src/retrieval-agents.test.ts +79 -0
  731. package/src/retrieval-agents.ts +71 -19
  732. package/src/salvage-envelope.ts +28 -0
  733. package/src/scopes/scope-plan.test.ts +42 -0
  734. package/src/scopes/scope-plan.ts +45 -0
  735. package/src/search/port.ts +13 -0
  736. package/src/secure-store/index.ts +1 -0
  737. package/src/secure-store/secure-fs.ts +48 -0
  738. package/src/secure-store/secure-store.test.ts +40 -0
  739. package/src/semantic-rule-promotion.ts +13 -4
  740. package/src/storage/content-hash-index.ts +655 -0
  741. package/src/storage/entity-timeline.ts +473 -0
  742. package/src/storage/memory-lifecycle-ledger-access.ts +603 -0
  743. package/src/storage/secure-line-reader.test.ts +197 -0
  744. package/src/storage/secure-line-reader.ts +181 -3
  745. package/src/storage-append-header-sniff.test.ts +195 -0
  746. package/src/storage-behavior-signals-cache.test.ts +237 -0
  747. package/src/storage-contract/cache-coherence.test.ts +96 -1
  748. package/src/storage-contract/content-hash.test.ts +47 -2
  749. package/src/storage-fact-hash-index-defer.test.ts +1200 -0
  750. package/src/storage-memory-lifecycle-ledger.test.ts +1146 -3
  751. package/src/storage.ts +882 -890
  752. package/src/structured-attributes.ts +52 -0
  753. package/src/temporal-index-batch.ts +112 -0
  754. package/src/temporal-index.test.ts +193 -0
  755. package/src/temporal-index.ts +49 -53
  756. package/src/testing/lifecycle-matrix.test.ts +212 -0
  757. package/src/testing/lifecycle-matrix.ts +271 -0
  758. package/src/testing/orchestrator-lite.ts +236 -0
  759. package/src/testing/subjects/dreams-ledger.test.ts +67 -0
  760. package/src/testing/subjects/extraction-lifecycle.test.ts +442 -0
  761. package/src/testing/subjects/lifecycle-ledger.test.ts +93 -0
  762. package/src/testing/subjects/recall-budget.test.ts +461 -0
  763. package/src/testing/subjects/serialized-write-chain.test.ts +224 -0
  764. package/src/trust-score-stage.test.ts +154 -0
  765. package/src/trust-score-stage.ts +216 -53
  766. package/src/types-continuity.ts +57 -0
  767. package/src/types.ts +51 -77
  768. package/src/utils/path-containment.test.ts +75 -0
  769. package/src/utils/path-containment.ts +76 -0
  770. package/src/utils/serialize-mutations.test.ts +122 -0
  771. package/src/utils/serialize-mutations.ts +68 -3
  772. package/src/wearables/memory-gen.test.ts +31 -9
  773. package/src/wearables/memory-gen.ts +95 -55
  774. package/src/wearables/pipeline.test.ts +10 -7
  775. package/src/wearables/service.test.ts +6 -8
  776. package/src/wearables/service.ts +2 -2
  777. package/src/write-envelope.extension.test.ts +111 -0
  778. package/src/write-envelope.test.ts +683 -0
  779. package/src/write-envelope.ts +902 -0
  780. package/src/write-quarantine.test.ts +245 -0
  781. package/src/write-quarantine.ts +290 -0
  782. package/src/write-rate-limiter.test.ts +105 -0
  783. package/src/write-rate-limiter.ts +135 -0
  784. package/src/write-sealed-parity.test.ts +204 -0
  785. package/dist/chunk-2VN4JAX7.js.map +0 -1
  786. package/dist/chunk-37CTGERM.js.map +0 -1
  787. package/dist/chunk-3IY64MV6.js.map +0 -1
  788. package/dist/chunk-BCDKBDSY.js.map +0 -1
  789. package/dist/chunk-BFPETA3S.js.map +0 -1
  790. package/dist/chunk-BFVEEB7W.js.map +0 -1
  791. package/dist/chunk-BS4SGLWV.js.map +0 -1
  792. package/dist/chunk-DCFV7A7H.js.map +0 -1
  793. package/dist/chunk-DYP3YYJJ.js.map +0 -1
  794. package/dist/chunk-E5BK6CGV.js +0 -335
  795. package/dist/chunk-E5BK6CGV.js.map +0 -1
  796. package/dist/chunk-FTYDWKO6.js.map +0 -1
  797. package/dist/chunk-GWTFLF2P.js.map +0 -1
  798. package/dist/chunk-IKQTFQMI.js.map +0 -1
  799. package/dist/chunk-INELTPRG.js.map +0 -1
  800. package/dist/chunk-J2HSAU72.js.map +0 -1
  801. package/dist/chunk-JREB6UFH.js.map +0 -1
  802. package/dist/chunk-K7LOWSLU.js.map +0 -1
  803. package/dist/chunk-KQDNAXOM.js.map +0 -1
  804. package/dist/chunk-KSBEEMQF.js.map +0 -1
  805. package/dist/chunk-KSNVDPDF.js.map +0 -1
  806. package/dist/chunk-LC7OODWZ.js.map +0 -1
  807. package/dist/chunk-LTJAMRGI.js.map +0 -1
  808. package/dist/chunk-LZOV7MX7.js.map +0 -1
  809. package/dist/chunk-NWTGD4AN.js.map +0 -1
  810. package/dist/chunk-ODPLEWB6.js.map +0 -1
  811. package/dist/chunk-OVJ2HIPE.js.map +0 -1
  812. package/dist/chunk-PBSAPHHA.js.map +0 -1
  813. package/dist/chunk-PHK3HARR.js +0 -32
  814. package/dist/chunk-PHK3HARR.js.map +0 -1
  815. package/dist/chunk-PLQE6MJA.js.map +0 -1
  816. package/dist/chunk-QKQYXZVS.js.map +0 -1
  817. package/dist/chunk-QQGPUKMT.js.map +0 -1
  818. package/dist/chunk-RANHFI3P.js +0 -8
  819. package/dist/chunk-RANHFI3P.js.map +0 -1
  820. package/dist/chunk-RAR2BSEM.js.map +0 -1
  821. package/dist/chunk-RULE4VG5.js.map +0 -1
  822. package/dist/chunk-SVOZFLIQ.js +0 -16
  823. package/dist/chunk-SVOZFLIQ.js.map +0 -1
  824. package/dist/chunk-T7Y5KPLN.js.map +0 -1
  825. package/dist/chunk-TXLGHDHS.js.map +0 -1
  826. package/dist/chunk-VDW4VU7B.js.map +0 -1
  827. package/dist/chunk-VSMRDBFT.js +0 -17
  828. package/dist/chunk-VSMRDBFT.js.map +0 -1
  829. package/dist/chunk-X64YLC24.js.map +0 -1
  830. package/dist/chunk-XDSXEKYK.js +0 -118
  831. package/dist/chunk-XDSXEKYK.js.map +0 -1
  832. package/dist/chunk-YF6FMLBT.js.map +0 -1
  833. package/dist/resolution-VH4NGENZ.js +0 -12
  834. /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-citation.js.map} +0 -0
  835. /package/dist/{contradiction-scan-J57T5QV7.js.map → access-memory-search-fanout.js.map} +0 -0
  836. /package/dist/{dreams-ledger-6UODQOX6.js.map → access-recall-concurrency.js.map} +0 -0
  837. /package/dist/{graph-edge-decay-IOGBBAGC.js.map → access-recall-response.js.map} +0 -0
  838. /package/dist/{resolution-VH4NGENZ.js.map → bounded-jsonl-state.js.map} +0 -0
  839. /package/dist/{chunk-A3GOMRJG.js.map → chunk-23L3IXFD.js.map} +0 -0
  840. /package/dist/{chunk-MRHFJV44.js.map → chunk-2DQ2XREU.js.map} +0 -0
  841. /package/dist/{chunk-L2B3EE7B.js.map → chunk-2QMKMVHH.js.map} +0 -0
  842. /package/dist/{chunk-VJVNLNDQ.js.map → chunk-2UFO7X4Q.js.map} +0 -0
  843. /package/dist/{chunk-2MR3MFQB.js.map → chunk-2VEOKUPP.js.map} +0 -0
  844. /package/dist/{chunk-RHOZ5LLO.js.map → chunk-347WD2LE.js.map} +0 -0
  845. /package/dist/{chunk-HDTQVNWD.js.map → chunk-3QAH2S33.js.map} +0 -0
  846. /package/dist/{chunk-LRNMRXJO.js.map → chunk-3Y224NYC.js.map} +0 -0
  847. /package/dist/{chunk-VFSP73LP.js.map → chunk-45GF7Y3V.js.map} +0 -0
  848. /package/dist/{chunk-OEHCMALU.js.map → chunk-5I4M26Q7.js.map} +0 -0
  849. /package/dist/{chunk-2AZXIZEN.js.map → chunk-5Q7EVV4X.js.map} +0 -0
  850. /package/dist/{chunk-PUBTHW5O.js.map → chunk-6DRPPQEJ.js.map} +0 -0
  851. /package/dist/{chunk-FDCTQWLW.js.map → chunk-6O22TZSB.js.map} +0 -0
  852. /package/dist/{chunk-ZQ7UBUZ6.js.map → chunk-6QYST5XN.js.map} +0 -0
  853. /package/dist/{chunk-W4PGTAS2.js.map → chunk-6WAFYNVT.js.map} +0 -0
  854. /package/dist/{chunk-R5VAVVHP.js.map → chunk-72A4L5JJ.js.map} +0 -0
  855. /package/dist/{chunk-3FAMU5TX.js.map → chunk-A6USBZTO.js.map} +0 -0
  856. /package/dist/{chunk-ZHIVCODT.js.map → chunk-A72SWTWW.js.map} +0 -0
  857. /package/dist/{chunk-FC77VUX5.js.map → chunk-A7Q43EUZ.js.map} +0 -0
  858. /package/dist/{chunk-UJBESW6X.js.map → chunk-AE7WMV4V.js.map} +0 -0
  859. /package/dist/{chunk-A5UXZRRM.js.map → chunk-AJ7QCSIS.js.map} +0 -0
  860. /package/dist/{chunk-FNMFJWBY.js.map → chunk-ANEUPF6O.js.map} +0 -0
  861. /package/dist/{chunk-2A3OXAP2.js.map → chunk-B7RWBZXW.js.map} +0 -0
  862. /package/dist/{chunk-74F5STS4.js.map → chunk-BLAKS3CP.js.map} +0 -0
  863. /package/dist/{chunk-M7VGG3ID.js.map → chunk-BV5WMSIJ.js.map} +0 -0
  864. /package/dist/{chunk-2BFLDH5F.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  865. /package/dist/{chunk-UXKMMNJD.js.map → chunk-C23BYTJV.js.map} +0 -0
  866. /package/dist/{chunk-JZHHREBL.js.map → chunk-C3AKM55W.js.map} +0 -0
  867. /package/dist/{chunk-RP7YXA67.js.map → chunk-C5JF6EZ7.js.map} +0 -0
  868. /package/dist/{chunk-EHCONI6E.js.map → chunk-CQMEM5CC.js.map} +0 -0
  869. /package/dist/{chunk-PBNZVPFS.js.map → chunk-CXZQOVIE.js.map} +0 -0
  870. /package/dist/{chunk-7N2RUBAP.js.map → chunk-DDA47TES.js.map} +0 -0
  871. /package/dist/{chunk-64SE4X5G.js.map → chunk-DEBHXHJR.js.map} +0 -0
  872. /package/dist/{chunk-EHK6KZHK.js.map → chunk-DNHVPZOD.js.map} +0 -0
  873. /package/dist/{chunk-437S3G37.js.map → chunk-DQEMWVMT.js.map} +0 -0
  874. /package/dist/{chunk-LZZNTPLR.js.map → chunk-DX3XRGLH.js.map} +0 -0
  875. /package/dist/{chunk-DZKHSDOM.js.map → chunk-ES36HTSC.js.map} +0 -0
  876. /package/dist/{chunk-TN7THW5U.js.map → chunk-ET356KSB.js.map} +0 -0
  877. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  878. /package/dist/{chunk-DMSDUGDM.js.map → chunk-F7GAIWFA.js.map} +0 -0
  879. /package/dist/{chunk-G6XXLKC5.js.map → chunk-FFPJINJE.js.map} +0 -0
  880. /package/dist/{chunk-G4CN3ZXL.js.map → chunk-FUTF72MJ.js.map} +0 -0
  881. /package/dist/{chunk-HF77M34J.js.map → chunk-GFPZS3F5.js.map} +0 -0
  882. /package/dist/{chunk-G6NKALRN.js.map → chunk-H4WG3UQZ.js.map} +0 -0
  883. /package/dist/{chunk-UDQUSU27.js.map → chunk-HNZKVLAN.js.map} +0 -0
  884. /package/dist/{chunk-5EZXHU36.js.map → chunk-HYQJQGD3.js.map} +0 -0
  885. /package/dist/{chunk-TRWPCR6P.js.map → chunk-JECBQBEX.js.map} +0 -0
  886. /package/dist/{chunk-E6CBU7XP.js.map → chunk-JYIFGGEM.js.map} +0 -0
  887. /package/dist/{chunk-VA4WSW54.js.map → chunk-JYIJNNN3.js.map} +0 -0
  888. /package/dist/{chunk-L5HJQC36.js.map → chunk-K2TDK7GG.js.map} +0 -0
  889. /package/dist/{chunk-KDXVOFYY.js.map → chunk-KAVBJSBQ.js.map} +0 -0
  890. /package/dist/{chunk-4OKLXWDZ.js.map → chunk-KNB7IBBU.js.map} +0 -0
  891. /package/dist/{chunk-QLULYKWZ.js.map → chunk-KSXJOC4F.js.map} +0 -0
  892. /package/dist/{chunk-2S6QHZGJ.js.map → chunk-L2DPF4DF.js.map} +0 -0
  893. /package/dist/{chunk-JI5AVXYO.js.map → chunk-LEPOP46W.js.map} +0 -0
  894. /package/dist/{chunk-O7BBFJLP.js.map → chunk-LTAVSOZW.js.map} +0 -0
  895. /package/dist/{chunk-2IZAGAJR.js.map → chunk-LULR4IUW.js.map} +0 -0
  896. /package/dist/{chunk-WMWVO45V.js.map → chunk-MC6XO7T4.js.map} +0 -0
  897. /package/dist/{chunk-D6RIFG4O.js.map → chunk-MNB5RMAY.js.map} +0 -0
  898. /package/dist/{chunk-Q5WG43OP.js.map → chunk-MYFQ3ED4.js.map} +0 -0
  899. /package/dist/{chunk-V3UE5I5F.js.map → chunk-MYVPLJOO.js.map} +0 -0
  900. /package/dist/{chunk-MTWKDOVO.js.map → chunk-NLE6TSLG.js.map} +0 -0
  901. /package/dist/{chunk-DBRYEIGY.js.map → chunk-OJP3N7UK.js.map} +0 -0
  902. /package/dist/{chunk-SMX5267P.js.map → chunk-OZKOPQCM.js.map} +0 -0
  903. /package/dist/{chunk-JCK7V6SN.js.map → chunk-P65YJEEL.js.map} +0 -0
  904. /package/dist/{chunk-MTR7BZQN.js.map → chunk-PBGWYM3H.js.map} +0 -0
  905. /package/dist/{chunk-AXTXN4T6.js.map → chunk-PWFCHOB3.js.map} +0 -0
  906. /package/dist/{chunk-FDPZVY6D.js.map → chunk-QFN4U7AG.js.map} +0 -0
  907. /package/dist/{chunk-K2ZLHESQ.js.map → chunk-QIEUU4UK.js.map} +0 -0
  908. /package/dist/{chunk-3D6AFGQR.js.map → chunk-QVT3E4CE.js.map} +0 -0
  909. /package/dist/{chunk-SFQ6QNL7.js.map → chunk-R6YJI3HE.js.map} +0 -0
  910. /package/dist/{chunk-5CJVLG3H.js.map → chunk-RFDD3B7W.js.map} +0 -0
  911. /package/dist/{chunk-JBPKEARU.js.map → chunk-RKNJBZ55.js.map} +0 -0
  912. /package/dist/{chunk-FNZQGN76.js.map → chunk-S2BBISHU.js.map} +0 -0
  913. /package/dist/{chunk-IALLX7EE.js.map → chunk-S2FSBU4H.js.map} +0 -0
  914. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  915. /package/dist/{chunk-DPAENM7O.js.map → chunk-U3ZAVRP3.js.map} +0 -0
  916. /package/dist/{chunk-4KJLYTLS.js.map → chunk-V57NLO5P.js.map} +0 -0
  917. /package/dist/{chunk-FLVL4L3L.js.map → chunk-VDX2J7OX.js.map} +0 -0
  918. /package/dist/{chunk-AJLLIIML.js.map → chunk-W4FBGYGK.js.map} +0 -0
  919. /package/dist/{chunk-6SXVCD7W.js.map → chunk-W7IGLX4G.js.map} +0 -0
  920. /package/dist/{chunk-KAWTOYAW.js.map → chunk-WLZYGLJ4.js.map} +0 -0
  921. /package/dist/{chunk-RO4LQGT3.js.map → chunk-WON4APQY.js.map} +0 -0
  922. /package/dist/{chunk-PQVK2S3J.js.map → chunk-WZFAPHIK.js.map} +0 -0
  923. /package/dist/{chunk-KQPJLK3X.js.map → chunk-XL24QQ63.js.map} +0 -0
  924. /package/dist/{chunk-RVZRYUQH.js.map → chunk-YROFNEG5.js.map} +0 -0
  925. /package/dist/{chunk-VRAD2FGO.js.map → chunk-Z54EM5YZ.js.map} +0 -0
  926. /package/dist/{first-start-migration-TI67YMLG.js.map → first-start-migration-CSRHCJIQ.js.map} +0 -0
  927. /package/dist/{forget-CQCLB5LB.js.map → forget-HRPLVB3W.js.map} +0 -0
  928. /package/dist/{purge-NDWV6WYD.js.map → purge-IRF47ZTX.js.map} +0 -0
  929. /package/dist/{tier-stats-KV7IWCJR.js.map → tier-stats-QEJSPFV2.js.map} +0 -0
@@ -1,12 +1,29 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { appendFile, chmod, lstat, mkdir, mkdtemp, readdir, readFile, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
+ import { randomUUID } from "node:crypto";
6
7
 
7
8
  import { StorageManager } from "./storage.js";
8
- import { encryptFileBody, filePathAad } from "./secure-store/secure-fs.js";
9
+ import { encryptFileBody, filePathAad, isEncryptedFile, readMaybeEncryptedFile, readMaybeEncryptedFileBuffer } from "./secure-store/secure-fs.js";
9
10
  import type { MemoryLifecycleEvent } from "./types.js";
11
+ import {
12
+ appendLifecycleEventsSerialized,
13
+ drainPendingLifecycleAppendsSerialized,
14
+ pendingLifecycleLedgerDir,
15
+ readAllLifecycleEventsFromLedgerBuffer,
16
+ type LifecyclePendingIo,
17
+ } from "./storage/memory-lifecycle-ledger-access.js";
18
+ import { withHeldFileLock } from "./utils/serialize-mutations.js";
19
+ import { listContainedSpillFiles } from "./utils/path-containment.js";
20
+ import { rebuildMemoryLifecycleLedger } from "./maintenance/rebuild-memory-lifecycle-ledger.js";
21
+ import type { RebuildMemoryLifecycleLedgerResult } from "./maintenance/rebuild-memory-lifecycle-ledger.js";
22
+ import {
23
+ memoryLifecycleLedgerLockPath,
24
+ MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS,
25
+ MEMORY_LIFECYCLE_LEDGER_APPEND_LOCK_MAX_WAIT_MS,
26
+ } from "./memory-lifecycle-ledger-utils.js";
10
27
 
11
28
  function lifecycleEvent(
12
29
  eventId: string,
@@ -67,7 +84,7 @@ test("readAllMemoryLifecycleEvents streams plaintext rows and preserves fail-ope
67
84
  });
68
85
  });
69
86
 
70
- test("readMemoryLifecycleEvents applies its limit after streamed lifecycle sorting", async () => {
87
+ test("readMemoryLifecycleEvents returns the canonical last-N tail, not the append tail", async () => {
71
88
  const first = lifecycleEvent("event-1", "memory-a", "2026-01-01T00:00:00.000Z");
72
89
  const second = lifecycleEvent("event-2", "memory-a", "2026-01-02T00:00:00.000Z");
73
90
  const third = lifecycleEvent("event-3", "memory-b", "2026-01-01T00:00:00.000Z");
@@ -77,10 +94,133 @@ test("readMemoryLifecycleEvents applies its limit after streamed lifecycle sorti
77
94
  JSON.stringify(first),
78
95
  JSON.stringify(second),
79
96
  ], async (storage) => {
97
+ // The bounded read ranks by the canonical comparator (memoryId, timestamp,
98
+ // eventType), then keeps the last 2 — identical to the pre-#1910 readAll →
99
+ // sort → slice(-limit). Canonical order is [first, second, third], so the
100
+ // last two are [second, third] regardless of append order (#1910,
101
+ // CodeRabbit: keep the no-memoryId read on the canonical tail).
80
102
  assert.deepEqual(await storage.readMemoryLifecycleEvents(2), [second, third]);
81
103
  });
82
104
  });
83
105
 
106
+ test("readMemoryLifecycleEvents(MAX_SAFE_INTEGER) equals readAllMemoryLifecycleEvents", async () => {
107
+ const first = lifecycleEvent("event-1", "memory-a", "2026-01-01T00:00:00.000Z");
108
+ const second = lifecycleEvent("event-2", "memory-a", "2026-01-02T00:00:00.000Z");
109
+ const third = lifecycleEvent("event-3", "memory-b", "2026-01-01T00:00:00.000Z");
110
+
111
+ await withLifecycleLedger([
112
+ JSON.stringify(third),
113
+ JSON.stringify(first),
114
+ JSON.stringify(second),
115
+ ], async (storage) => {
116
+ const all = await storage.readAllMemoryLifecycleEvents();
117
+ const capped = await storage.readMemoryLifecycleEvents(Number.MAX_SAFE_INTEGER);
118
+ assert.deepEqual(capped, all);
119
+ });
120
+ });
121
+
122
+ test("readMemoryLifecycleEvents(MAX_SAFE_INTEGER) admits unknown eventTypes exactly like readAll", async () => {
123
+ const known = lifecycleEvent("event-1", "memory-a", "2026-01-01T00:00:00.000Z");
124
+ // Structurally valid row whose eventType is NOT in the handled sort table.
125
+ // readAll admits any string eventType, so the bounded governance read must
126
+ // too — dropping it would silently diverge the projection-rebuild scan.
127
+ const unknown = {
128
+ eventId: "event-2",
129
+ memoryId: "memory-a",
130
+ eventType: "quantum_entangled",
131
+ timestamp: "2026-01-02T00:00:00.000Z",
132
+ actor: "test",
133
+ ruleVersion: "1",
134
+ };
135
+
136
+ await withLifecycleLedger([
137
+ JSON.stringify(known),
138
+ JSON.stringify(unknown),
139
+ ], async (storage) => {
140
+ const all = await storage.readAllMemoryLifecycleEvents();
141
+ const capped = await storage.readMemoryLifecycleEvents(Number.MAX_SAFE_INTEGER);
142
+ assert.equal(all.length, 2, "readAll keeps the unknown-but-valid row");
143
+ assert.deepEqual(capped, all, "governance read must not drop rows readAll admits");
144
+ assert.ok(
145
+ capped.map((event) => event.eventId).includes("event-2"),
146
+ "unknown eventType row survives the governance read",
147
+ );
148
+ });
149
+ });
150
+
151
+ test("readMemoryLifecycleEvents tail read never triggers a whole-file secure read on plaintext", async () => {
152
+ const rows: string[] = [];
153
+ for (let i = 0; i < 500; i += 1) {
154
+ const stamp = `2026-01-01T00:00:${String(i % 60).padStart(2, "0")}.000Z`;
155
+ rows.push(JSON.stringify(lifecycleEvent(`event-${i}`, `memory-${i}`, stamp)));
156
+ }
157
+ await withLifecycleLedger(rows, async (storage) => {
158
+ let secureWholeFileReads = 0;
159
+ const privateStorage = storage as unknown as {
160
+ readStorageSecureFile(filePath: string): Promise<string>;
161
+ };
162
+ privateStorage.readStorageSecureFile = async () => {
163
+ secureWholeFileReads += 1;
164
+ throw new Error("plaintext lifecycle ledger must not use whole-file reads");
165
+ };
166
+ const tail = await storage.readMemoryLifecycleEvents(200);
167
+ assert.equal(tail.length, 200);
168
+ assert.equal(secureWholeFileReads, 0);
169
+ });
170
+ });
171
+
172
+ test("getMemoryTimeline fallback streams per-memory rows and warns", async () => {
173
+ const target = "memory-target";
174
+ const rows: string[] = [];
175
+ for (let i = 0; i < 10; i += 1) {
176
+ // Target events appended in strictly increasing timestamp order — the
177
+ // production invariant that makes ring-of-last-N-then-sort equal to the
178
+ // old filter→slice(-N).
179
+ const stamp = `2026-01-01T00:${String(i).padStart(2, "0")}:00.000Z`;
180
+ rows.push(JSON.stringify(lifecycleEvent(`other-${i}`, `memory-other-${i}`, stamp)));
181
+ rows.push(JSON.stringify(lifecycleEvent(`t-${i}`, target, stamp)));
182
+ }
183
+ await withLifecycleLedger(rows, async (storage) => {
184
+ const all = await storage.readAllMemoryLifecycleEvents();
185
+ const expected = all.filter((e) => e.memoryId === target).slice(-5);
186
+ const timeline = await storage.getMemoryTimeline(target, 5);
187
+ assert.deepEqual(timeline, expected);
188
+ assert.ok(timeline.every((e) => e.memoryId === target));
189
+ });
190
+ });
191
+
192
+ test("getMemoryTimeline fallback keeps the newest-by-timestamp events when an older row is appended last", async () => {
193
+ const target = "memory-target";
194
+ // Target rows appended in this order; the LAST-appended row carries the
195
+ // OLDEST timestamp (a late backfill). The old append-tail-then-sort kept the
196
+ // last N appended rows — so it would drop t2 (00:02, newer) and surface the
197
+ // stale t5 (00:00). The canonical top-N must instead return the N newest by
198
+ // timestamp, matching the pre-bounding filter→sort→slice semantics.
199
+ const rows = [
200
+ JSON.stringify(lifecycleEvent("other-a", "memory-other-a", "2026-01-01T00:05:00.000Z")),
201
+ JSON.stringify(lifecycleEvent("t1", target, "2026-01-01T00:01:00.000Z")),
202
+ JSON.stringify(lifecycleEvent("t2", target, "2026-01-01T00:02:00.000Z")),
203
+ JSON.stringify(lifecycleEvent("other-b", "memory-other-b", "2026-01-01T00:06:00.000Z")),
204
+ JSON.stringify(lifecycleEvent("t3", target, "2026-01-01T00:03:00.000Z")),
205
+ JSON.stringify(lifecycleEvent("t4", target, "2026-01-01T00:04:00.000Z")),
206
+ // Late-appended backfill with an older timestamp than t2/t3/t4.
207
+ JSON.stringify(lifecycleEvent("t5", target, "2026-01-01T00:00:00.000Z")),
208
+ ];
209
+ await withLifecycleLedger(rows, async (storage) => {
210
+ const all = await storage.readAllMemoryLifecycleEvents();
211
+ const expected = all.filter((e) => e.memoryId === target).slice(-3);
212
+ const timeline = await storage.getMemoryTimeline(target, 3);
213
+ // Newest three by timestamp: t2 (00:02), t3 (00:03), t4 (00:04). The stale
214
+ // last-appended t5 (00:00) is excluded.
215
+ assert.deepEqual(
216
+ timeline.map((e) => e.eventId),
217
+ ["t2", "t3", "t4"],
218
+ );
219
+ assert.deepEqual(timeline, expected);
220
+ assert.ok(timeline.every((e) => e.memoryId === target));
221
+ });
222
+ });
223
+
84
224
  test("readAllMemoryLifecycleEvents keeps the authenticated encrypted-file fallback", async () => {
85
225
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lifecycle-encrypted-"));
86
226
  const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
@@ -100,6 +240,58 @@ test("readAllMemoryLifecycleEvents keeps the authenticated encrypted-file fallba
100
240
  }
101
241
  });
102
242
 
243
+ test("readAllMemoryLifecycleEventsForCompaction reads an encrypted ledger via the uncapped buffer path (#2033)", async () => {
244
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lifecycle-compaction-read-"));
245
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
246
+ const a = lifecycleEvent("evt-a", "memory-a", "2026-01-01T00:00:00.000Z");
247
+ const b = lifecycleEvent("evt-b", "memory-b", "2026-01-02T00:00:00.000Z");
248
+ const key = Buffer.alloc(32, 3);
249
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
250
+ await writeFile(
251
+ ledgerPath,
252
+ encryptFileBody(
253
+ `${JSON.stringify(a)}\n${JSON.stringify(b)}\n`,
254
+ key,
255
+ filePathAad(ledgerPath, memoryDir),
256
+ ),
257
+ );
258
+ try {
259
+ const storage = new StorageManager(memoryDir);
260
+ storage.setSecureStoreKey(key);
261
+ // The compaction read must decrypt the same rows the general capped read
262
+ // returns — but through the buffer path, so it also works past the
263
+ // whole-file string decrypt cap that would otherwise block compaction.
264
+ assert.deepEqual(await storage.readAllMemoryLifecycleEventsForCompaction(), [a, b]);
265
+ assert.deepEqual(
266
+ await storage.readAllMemoryLifecycleEventsForCompaction(),
267
+ await storage.readAllMemoryLifecycleEvents(),
268
+ );
269
+ } finally {
270
+ await rm(memoryDir, { recursive: true, force: true });
271
+ }
272
+ });
273
+
274
+ test("readAllMemoryLifecycleEventsForCompaction streams a plaintext ledger", async () => {
275
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lifecycle-plaintext-compaction-"));
276
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
277
+ const a = lifecycleEvent("evt-plain-a", "memory-a", "2026-01-01T00:00:00.000Z");
278
+ const b = lifecycleEvent("evt-plain-b", "memory-b", "2026-01-02T00:00:00.000Z");
279
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
280
+ await writeFile(ledgerPath, `${JSON.stringify(a)}\n${JSON.stringify(b)}\n`, "utf8");
281
+ try {
282
+ const storage = new StorageManager(memoryDir);
283
+ const privateStorage = storage as unknown as {
284
+ readStorageSecureFile(filePath: string): Promise<string>;
285
+ };
286
+ privateStorage.readStorageSecureFile = async () => {
287
+ throw new Error("plaintext lifecycle ledger must stream without whole-file reads");
288
+ };
289
+ assert.deepEqual(await storage.readAllMemoryLifecycleEventsForCompaction(), [a, b]);
290
+ } finally {
291
+ await rm(memoryDir, { recursive: true, force: true });
292
+ }
293
+ });
294
+
103
295
  test("readMemoryActionEventRows streams the action ledger and keeps source line numbers", async () => {
104
296
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-action-stream-"));
105
297
  const ledgerPath = path.join(memoryDir, "state", "memory-actions.jsonl");
@@ -128,3 +320,954 @@ test("readMemoryActionEventRows streams the action ledger and keeps source line
128
320
  await rm(memoryDir, { recursive: true, force: true });
129
321
  }
130
322
  });
323
+
324
+ test("appendLifecycleEventsSerialized waits out a held ledger lock instead of dropping the event (#2033)", async () => {
325
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-wait-"));
326
+ try {
327
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
328
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
329
+ await writeFile(ledgerPath, "", "utf8");
330
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
331
+
332
+ // Simulate a compaction rewrite holding the shared lock while the append
333
+ // contends for it. The append must WAIT for the holder to release rather
334
+ // than give up and drop the event fail-open (the pre-#2033 behavior at 5s).
335
+ // Ordering is driven by gate promises — never wall-clock sleeps — so the
336
+ // holder provably owns the lock before the append starts and only releases
337
+ // when we open the gate.
338
+ const order: string[] = [];
339
+ const acquiredGate = Promise.withResolvers<void>();
340
+ const releaseGate = Promise.withResolvers<void>();
341
+ const holder = withHeldFileLock(
342
+ lockPath,
343
+ { staleMs: MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS, maxWaitMs: 1_000 },
344
+ async (acquired) => {
345
+ assert.ok(acquired, "test holder must acquire the lock");
346
+ acquiredGate.resolve();
347
+ await releaseGate.promise;
348
+ order.push("holder-released");
349
+ },
350
+ );
351
+ // The lock is provably held from here until we open the release gate.
352
+ await acquiredGate.promise;
353
+
354
+ const appendPromise = appendLifecycleEventsSerialized(
355
+ ledgerPath,
356
+ async (payload) => {
357
+ order.push("append-ran");
358
+ await appendFile(ledgerPath, payload, "utf8");
359
+ },
360
+ `${JSON.stringify(lifecycleEvent("evt-wait", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
361
+ undefined,
362
+ { maxWaitMs: 2_000, pollMs: 20 },
363
+ );
364
+
365
+ // Release the holder; the append can only acquire after this.
366
+ releaseGate.resolve();
367
+ await appendPromise;
368
+ await holder;
369
+
370
+ assert.deepEqual(
371
+ order,
372
+ ["holder-released", "append-ran"],
373
+ "append must acquire only after the holder released — it waited, not dropped",
374
+ );
375
+ assert.ok(
376
+ (await readFile(ledgerPath, "utf8")).includes("evt-wait"),
377
+ "the appended event must be persisted after the wait",
378
+ );
379
+ } finally {
380
+ await rm(dir, { recursive: true, force: true });
381
+ }
382
+ });
383
+
384
+ test("appendLifecycleEventsSerialized refuses to append unlocked when the lock cannot be acquired (#2033)", async () => {
385
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-refuse-"));
386
+ try {
387
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
388
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
389
+ await writeFile(ledgerPath, "", "utf8");
390
+ // A fresh (non-stale) foreign lock held for the whole test so acquisition
391
+ // times out within the tiny budget and never stale-breaks. The append must
392
+ // REFUSE to write unlocked rather than risk clobbering a concurrent rewrite.
393
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
394
+ await writeFile(lockPath, `${process.pid} held-by-test ${new Date().toISOString()}\n`, "utf8");
395
+
396
+ let appended = false;
397
+ await assert.rejects(
398
+ () => appendLifecycleEventsSerialized(
399
+ ledgerPath,
400
+ async () => {
401
+ appended = true;
402
+ },
403
+ "x\n",
404
+ undefined,
405
+ { maxWaitMs: 100, pollMs: 20 },
406
+ ),
407
+ /could not acquire the ledger lock/,
408
+ );
409
+ assert.equal(appended, false, "must not append unlocked when the lock is unavailable");
410
+ assert.equal(await readFile(ledgerPath, "utf8"), "", "ledger must be untouched");
411
+ } finally {
412
+ await rm(dir, { recursive: true, force: true });
413
+ }
414
+ });
415
+
416
+ test("lifecycle append lock budget outlasts a compaction that holds the lock past 5s (#2033)", () => {
417
+ // withHeldFileLock's default acquisition budget is 5s; a compaction rewrite of
418
+ // a large ledger can legitimately hold the lock longer. The append budget must
419
+ // cover the full stale-break window so such a hold is waited out (or a crashed
420
+ // holder stale-broken) rather than dropping the event at 5s.
421
+ assert.ok(
422
+ MEMORY_LIFECYCLE_LEDGER_APPEND_LOCK_MAX_WAIT_MS >= MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS,
423
+ "append budget must be at least the stale window",
424
+ );
425
+ assert.ok(
426
+ MEMORY_LIFECYCLE_LEDGER_APPEND_LOCK_MAX_WAIT_MS > 5_000,
427
+ "append budget must exceed the 5s default that previously dropped events",
428
+ );
429
+ });
430
+
431
+ /** Plaintext pending IO for the access-layer tests (the StorageManager
432
+ * integration test below exercises the encrypted-at-rest path). Faithful to the
433
+ * `LifecyclePendingIo` contract: `writeSecure` is ATOMIC — it writes to a temp
434
+ * file (a non-`*.jsonl` name the drain's lister skips) in the same dir, then
435
+ * renames it onto the final path, so a concurrent drain never sees a partial
436
+ * spill (#2033). */
437
+ function plaintextPendingIo(): LifecyclePendingIo {
438
+ return {
439
+ writeSecure: async (p, c) => {
440
+ await mkdir(path.dirname(p), { recursive: true });
441
+ const temp = `${p}.tmp-${randomUUID()}`;
442
+ await writeFile(temp, c, "utf8");
443
+ await rename(temp, p);
444
+ },
445
+ readSecure: (p) => readFile(p, "utf8"),
446
+ };
447
+ }
448
+
449
+ test("appendLifecycleEventsSerialized spills to the durable pending queue instead of dropping when the lock is unavailable (#2033)", async () => {
450
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-"));
451
+ try {
452
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
453
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
454
+ await writeFile(ledgerPath, "", "utf8");
455
+ // A fresh (non-stale) foreign lock held for the whole test so acquisition
456
+ // times out within the tiny budget and never stale-breaks.
457
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
458
+ await writeFile(lockPath, `${process.pid} held-by-test ${new Date().toISOString()}\n`, "utf8");
459
+
460
+ const pending = plaintextPendingIo();
461
+ let ledgerAppended = false;
462
+ // MUST NOT throw: the event is durably queued, not dropped fail-open.
463
+ await appendLifecycleEventsSerialized(
464
+ ledgerPath,
465
+ async () => { ledgerAppended = true; },
466
+ `${JSON.stringify(lifecycleEvent("evt-spill", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
467
+ pending,
468
+ { maxWaitMs: 100, pollMs: 20 },
469
+ );
470
+ assert.equal(ledgerAppended, false, "lock unavailable — ledger not written directly");
471
+ assert.equal(await readFile(ledgerPath, "utf8"), "", "ledger untouched while lock held");
472
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
473
+ const files = await readdir(spillDir);
474
+ assert.equal(files.length, 1, "exactly one spill file queued");
475
+ assert.ok(
476
+ (await readFile(path.join(spillDir, files[0]!), "utf8")).includes("evt-spill"),
477
+ "event durably queued in the pending spill",
478
+ );
479
+ } finally {
480
+ await rm(dir, { recursive: true, force: true });
481
+ }
482
+ });
483
+
484
+ test("a concurrent drain never folds a partial spill; the finished spill is valid (#2033)", async () => {
485
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-atomic-"));
486
+ try {
487
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
488
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
489
+ await writeFile(ledgerPath, "", "utf8");
490
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
491
+ // Hold the ledger lock so the append cannot acquire it and must spill —
492
+ // mirroring a compaction rewrite holding the lock while a lock-denied append
493
+ // writes its spill WITHOUT the lock, concurrent with the holder's drain.
494
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
495
+ await writeFile(lockPath, `${process.pid} held-by-test ${new Date().toISOString()}\n`, "utf8");
496
+ const row = `${JSON.stringify(lifecycleEvent("evt-atomic", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`;
497
+
498
+ // Capture exactly what a concurrent fold-under-lock would enumerate + read
499
+ // (the fold uses listContainedSpillFiles + a lazy readSecure) WHILE the
500
+ // spill write is still in progress. An atomic writeSecure stages the full
501
+ // payload under a temp name the `*.jsonl` lister skips, so the drain sees
502
+ // nothing to fold until the rename lands the complete file (#2033).
503
+ let drainSawDuringWrite: Array<{ file: string; content: string }> = [];
504
+ const io: LifecyclePendingIo = {
505
+ writeSecure: async (p, c) => {
506
+ await mkdir(path.dirname(p), { recursive: true });
507
+ const temp = `${p}.tmp-${randomUUID()}`;
508
+ await writeFile(temp, c, "utf8");
509
+ const listed = await listContainedSpillFiles(spillDir);
510
+ drainSawDuringWrite = [];
511
+ for (const f of listed) drainSawDuringWrite.push({ file: f, content: await readFile(f, "utf8") });
512
+ await rename(temp, p);
513
+ },
514
+ readSecure: (p) => readFile(p, "utf8"),
515
+ };
516
+
517
+ await appendLifecycleEventsSerialized(
518
+ ledgerPath,
519
+ async () => { throw new Error("ledger append must not run while the lock is held"); },
520
+ row,
521
+ io,
522
+ { maxWaitMs: 100, pollMs: 20 },
523
+ );
524
+
525
+ assert.deepEqual(
526
+ drainSawDuringWrite,
527
+ [],
528
+ "a concurrent drain lists no in-progress spill, so it can never fold partial bytes",
529
+ );
530
+
531
+ // The finished spill is a single complete, valid row (never truncated).
532
+ const spillFiles = await listContainedSpillFiles(spillDir);
533
+ assert.equal(spillFiles.length, 1, "exactly one complete spill after the write");
534
+ const spillContent = await readFile(spillFiles[0]!, "utf8");
535
+ assert.equal(spillContent, row, "spill content is the whole payload, never a partial write");
536
+ assert.doesNotThrow(() => JSON.parse(spillContent.trim()), "final spill is valid JSON");
537
+
538
+ // Release the lock; a real drain folds exactly the one valid row.
539
+ await rm(lockPath, { force: true });
540
+ const drained = await drainPendingLifecycleAppendsSerialized(
541
+ ledgerPath,
542
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
543
+ io,
544
+ );
545
+ assert.equal(drained, true, "the completed spill drains into the ledger");
546
+ const ledger = (await readFile(ledgerPath, "utf8")).trim().split("\n").filter(Boolean);
547
+ assert.equal(ledger.length, 1, "exactly one row folded, none partial");
548
+ assert.equal(JSON.parse(ledger[0]!).eventId, "evt-atomic");
549
+ assert.equal((await readdir(spillDir)).length, 0, "spill removed after a successful drain");
550
+ } finally {
551
+ await rm(dir, { recursive: true, force: true });
552
+ }
553
+ });
554
+
555
+ test("a later append drains queued pending spills into the ledger (#2033)", async () => {
556
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drain-"));
557
+ try {
558
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
559
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
560
+ await writeFile(ledgerPath, "", "utf8");
561
+ const pending = plaintextPendingIo();
562
+ // Pre-seed two spilled events (as prior lock-timed-out appends would leave).
563
+ await pending.writeSecure(
564
+ path.join(pendingLifecycleLedgerDir(ledgerPath), "a.jsonl"),
565
+ `${JSON.stringify(lifecycleEvent("evt-old-1", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
566
+ );
567
+ await pending.writeSecure(
568
+ path.join(pendingLifecycleLedgerDir(ledgerPath), "b.jsonl"),
569
+ `${JSON.stringify(lifecycleEvent("evt-old-2", "memory-a", "2026-03-08T00:30:00.000Z"))}\n`,
570
+ );
571
+
572
+ await appendLifecycleEventsSerialized(
573
+ ledgerPath,
574
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
575
+ `${JSON.stringify(lifecycleEvent("evt-new", "memory-b", "2026-03-08T01:00:00.000Z"))}\n`,
576
+ pending,
577
+ );
578
+
579
+ const ledger = await readFile(ledgerPath, "utf8");
580
+ assert.ok(ledger.includes("evt-old-1"), "first spilled event folded into ledger");
581
+ assert.ok(ledger.includes("evt-old-2"), "second spilled event folded into ledger");
582
+ assert.ok(ledger.includes("evt-new"), "new event appended");
583
+ assert.equal(
584
+ (await readdir(pendingLifecycleLedgerDir(ledgerPath))).length,
585
+ 0,
586
+ "drained spill files removed",
587
+ );
588
+ } finally {
589
+ await rm(dir, { recursive: true, force: true });
590
+ }
591
+ });
592
+
593
+ test("drainPendingLifecycleAppendsSerialized folds queued events into the ledger without a new append (#2033)", async () => {
594
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drainonly-"));
595
+ try {
596
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
597
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
598
+ await writeFile(ledgerPath, "", "utf8");
599
+ const pending = plaintextPendingIo();
600
+ await pending.writeSecure(
601
+ path.join(pendingLifecycleLedgerDir(ledgerPath), "q.jsonl"),
602
+ `${JSON.stringify(lifecycleEvent("evt-queued", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
603
+ );
604
+
605
+ const drained = await drainPendingLifecycleAppendsSerialized(
606
+ ledgerPath,
607
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
608
+ pending,
609
+ );
610
+ assert.equal(drained, true, "reports rows drained");
611
+ assert.ok((await readFile(ledgerPath, "utf8")).includes("evt-queued"), "queued event in ledger");
612
+ assert.equal(
613
+ (await readdir(pendingLifecycleLedgerDir(ledgerPath))).length,
614
+ 0,
615
+ "drained spill files removed",
616
+ );
617
+
618
+ // Nothing pending → no-op that reports false.
619
+ const again = await drainPendingLifecycleAppendsSerialized(
620
+ ledgerPath,
621
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
622
+ pending,
623
+ );
624
+ assert.equal(again, false, "no pending rows → no drain");
625
+ } finally {
626
+ await rm(dir, { recursive: true, force: true });
627
+ }
628
+ });
629
+
630
+
631
+ test("pending drain commits oversized queues in bounded append batches (#2033)", async () => {
632
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drain-bounded-"));
633
+ try {
634
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
635
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
636
+ await writeFile(ledgerPath, "", "utf8");
637
+ const pending = plaintextPendingIo();
638
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
639
+ const first = `first:${"x".repeat(700 * 1024)}\n`;
640
+ const second = `second:${"y".repeat(700 * 1024)}\n`;
641
+ await pending.writeSecure(path.join(spillDir, "a.jsonl"), first);
642
+ await pending.writeSecure(path.join(spillDir, "b.jsonl"), second);
643
+ const batches: string[] = [];
644
+
645
+ const drained = await drainPendingLifecycleAppendsSerialized(
646
+ ledgerPath,
647
+ async (payload) => {
648
+ batches.push(payload);
649
+ await appendFile(ledgerPath, payload, "utf8");
650
+ },
651
+ pending,
652
+ );
653
+
654
+ assert.equal(drained, true, "rows were drained");
655
+ assert.equal(batches.length, 2, "each oversized spill is committed separately");
656
+ assert.ok(batches.every((payload) => Buffer.byteLength(payload, "utf8") <= 1024 * 1024));
657
+ assert.equal(await readFile(ledgerPath, "utf8"), first + second);
658
+ assert.equal((await readdir(spillDir)).length, 0, "all committed spills are removed");
659
+ } finally {
660
+ await rm(dir, { recursive: true, force: true });
661
+ }
662
+ });
663
+
664
+ test("a failed ledger write leaves pending spills intact for retry (#2033)", async () => {
665
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drainfail-"));
666
+ try {
667
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
668
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
669
+ await writeFile(ledgerPath, "", "utf8");
670
+ const pending = plaintextPendingIo();
671
+ await pending.writeSecure(
672
+ path.join(pendingLifecycleLedgerDir(ledgerPath), "q.jsonl"),
673
+ `${JSON.stringify(lifecycleEvent("evt-keep", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
674
+ );
675
+
676
+ await assert.rejects(
677
+ () => drainPendingLifecycleAppendsSerialized(
678
+ ledgerPath,
679
+ async () => { throw new Error("disk full"); },
680
+ pending,
681
+ ),
682
+ /disk full/,
683
+ );
684
+ assert.equal(
685
+ (await readdir(pendingLifecycleLedgerDir(ledgerPath))).length,
686
+ 1,
687
+ "spill file NOT deleted when the ledger write failed",
688
+ );
689
+ } finally {
690
+ await rm(dir, { recursive: true, force: true });
691
+ }
692
+ });
693
+
694
+ test("StorageManager drains an encrypted pending spill into the encrypted-at-rest ledger (#2033)", async () => {
695
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-enc-drain-"));
696
+ try {
697
+ const key = Buffer.alloc(32, 7);
698
+ const storage = new StorageManager(memoryDir);
699
+ storage.setSecureStoreKey(key);
700
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
701
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
702
+ // Seed an encrypted-at-rest ledger with a base event through the storage API.
703
+ await storage.appendMemoryLifecycleEvents([
704
+ lifecycleEvent("evt-base", "memory-a", "2026-03-08T00:00:00.000Z"),
705
+ ]);
706
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "precondition: ledger encrypted");
707
+
708
+ // Pre-seed an encrypted spill file exactly as a lock-timed-out append leaves
709
+ // it: encrypted at its own path-bound AAD.
710
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
711
+ const spillPath = path.join(spillDir, "spill.jsonl");
712
+ await mkdir(spillDir, { recursive: true });
713
+ const spillRow = `${JSON.stringify(lifecycleEvent("evt-spilled", "memory-b", "2026-03-08T01:00:00.000Z"))}\n`;
714
+ await writeFile(spillPath, encryptFileBody(spillRow, key, filePathAad(spillPath, memoryDir)));
715
+
716
+ const drained = await storage.drainPendingMemoryLifecycleEvents();
717
+ assert.equal(drained, true, "drain reports rows folded");
718
+ const ids = (await storage.readAllMemoryLifecycleEvents()).map((e) => e.eventId);
719
+ assert.ok(ids.includes("evt-base") && ids.includes("evt-spilled"), "both events readable from ledger");
720
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "ledger stays encrypted at rest after drain");
721
+ await assert.rejects(() => stat(spillPath), /ENOENT/, "spill file removed after drain");
722
+
723
+ // No pending → fast no-op.
724
+ assert.equal(await storage.drainPendingMemoryLifecycleEvents(), false, "no pending → no drain");
725
+ } finally {
726
+ await rm(memoryDir, { recursive: true, force: true });
727
+ }
728
+ });
729
+
730
+ test("pending drain rejects a symlinked spill entry pointing outside the pending dir (#2033)", async () => {
731
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-symlink-"));
732
+ const outsideDir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-outside-"));
733
+ try {
734
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
735
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
736
+ await writeFile(ledgerPath, "", "utf8");
737
+ const pending = plaintextPendingIo();
738
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
739
+
740
+ // A legitimate plaintext spill that MUST drain.
741
+ await pending.writeSecure(
742
+ path.join(spillDir, "legit.jsonl"),
743
+ `${JSON.stringify(lifecycleEvent("evt-legit", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
744
+ );
745
+ // A hostile secret OUTSIDE the pending dir, reached via a symlink whose name
746
+ // ends in .jsonl so a naive drain would readSecure (and later unlink) it.
747
+ const outsideSecret = path.join(outsideDir, "secret.txt");
748
+ const outsideContent = "top-secret-outside-the-store\n";
749
+ await writeFile(outsideSecret, outsideContent, "utf8");
750
+ await symlink(outsideSecret, path.join(spillDir, "evil.jsonl"));
751
+
752
+ const drained = await drainPendingLifecycleAppendsSerialized(
753
+ ledgerPath,
754
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
755
+ pending,
756
+ );
757
+
758
+ assert.equal(drained, true, "the legitimate spill still drained");
759
+ const ledger = await readFile(ledgerPath, "utf8");
760
+ assert.ok(ledger.includes("evt-legit"), "legit spill folded into the ledger");
761
+ assert.ok(
762
+ !ledger.includes("top-secret-outside-the-store"),
763
+ "symlinked outside file must NOT be read into the ledger",
764
+ );
765
+ // The outside target must be untouched: never read into the ledger, never
766
+ // unlinked through the symlink.
767
+ assert.equal(
768
+ await readFile(outsideSecret, "utf8"),
769
+ outsideContent,
770
+ "outside symlink target must survive the drain intact",
771
+ );
772
+ // The legit spill was drained (deleted); the hostile symlink was skipped
773
+ // (never read, never deleted) so it remains.
774
+ const remaining = await readdir(spillDir);
775
+ assert.deepEqual(remaining, ["evil.jsonl"], "only the skipped hostile symlink remains");
776
+ } finally {
777
+ await rm(dir, { recursive: true, force: true });
778
+ await rm(outsideDir, { recursive: true, force: true });
779
+ }
780
+ });
781
+
782
+ test("pending drain skips an unclaimable spill and later drains it exactly once — no duplicate (#2033)", async () => {
783
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drain-dup-"));
784
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
785
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
786
+ try {
787
+ await mkdir(spillDir, { recursive: true });
788
+ await writeFile(ledgerPath, "", "utf8");
789
+ const pending = plaintextPendingIo();
790
+ await pending.writeSecure(
791
+ path.join(spillDir, "q.jsonl"),
792
+ `${JSON.stringify(lifecycleEvent("evt-dup", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
793
+ );
794
+
795
+ // A read-only spill dir lets the read succeed but blocks the rename CLAIM
796
+ // (`q.jsonl` → `q.jsonl.claimed`, which needs directory write). Because the
797
+ // drain claims by rename BEFORE committing, an unclaimable spill is skipped
798
+ // this pass — never committed-then-left-behind — so a later drain cannot
799
+ // re-read and duplicate it.
800
+ await chmod(spillDir, 0o555);
801
+ const first = await drainPendingLifecycleAppendsSerialized(
802
+ ledgerPath,
803
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
804
+ pending,
805
+ );
806
+ assert.equal(first, false, "unclaimable spill is not drained this pass");
807
+ assert.ok(
808
+ !(await readFile(ledgerPath, "utf8")).includes("evt-dup"),
809
+ "unclaimable spill must NOT be written to the ledger",
810
+ );
811
+ assert.equal((await readdir(spillDir)).length, 1, "unclaimed spill remains for a later pass");
812
+
813
+ // Restore write permission; the spill drains exactly once.
814
+ await chmod(spillDir, 0o755);
815
+ const second = await drainPendingLifecycleAppendsSerialized(
816
+ ledgerPath,
817
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
818
+ pending,
819
+ );
820
+ assert.equal(second, true, "spill drains once permissions allow the claim");
821
+ const ledger = await readFile(ledgerPath, "utf8");
822
+ assert.equal(ledger.split("evt-dup").length - 1, 1, "event folded into the ledger exactly once — no duplicate");
823
+ assert.equal((await readdir(spillDir)).length, 0, "pending spill drained empty");
824
+ } finally {
825
+ await chmod(spillDir, 0o755).catch(() => undefined);
826
+ await rm(dir, { recursive: true, force: true });
827
+ }
828
+ });
829
+
830
+ test("pending drain recovers a claim orphaned by a crash before commit — the row is never lost (#2033)", async () => {
831
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drain-crash-"));
832
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
833
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
834
+ try {
835
+ await mkdir(spillDir, { recursive: true });
836
+ await writeFile(ledgerPath, "", "utf8");
837
+ const pending = plaintextPendingIo();
838
+ // Simulate a drain that CLAIMED a spill (renamed q.jsonl → q.jsonl.claimed)
839
+ // and then the process CRASHED before committing the rows to the ledger. The
840
+ // .claimed file is the ONLY durable copy of the event — plain unlink-before-
841
+ // commit would have already deleted it and lost the row.
842
+ await writeFile(
843
+ path.join(spillDir, "q.jsonl.claimed"),
844
+ `${JSON.stringify(lifecycleEvent("evt-orphan", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
845
+ "utf8",
846
+ );
847
+
848
+ const drained = await drainPendingLifecycleAppendsSerialized(
849
+ ledgerPath,
850
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
851
+ pending,
852
+ );
853
+
854
+ assert.equal(drained, true, "orphaned claim is recovered and committed");
855
+ const ledger = await readFile(ledgerPath, "utf8");
856
+ assert.ok(ledger.includes("evt-orphan"), "orphaned event folded into the ledger — not lost to the crash");
857
+ assert.equal(ledger.split("evt-orphan").length - 1, 1, "committed exactly once");
858
+ assert.equal((await readdir(spillDir)).length, 0, "recovered claim cleaned up after commit");
859
+ } finally {
860
+ await rm(dir, { recursive: true, force: true });
861
+ }
862
+ });
863
+
864
+ test("an append recovers a crash-orphaned claim alongside the new event (#2033)", async () => {
865
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-crash-fold-"));
866
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
867
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
868
+ try {
869
+ await mkdir(spillDir, { recursive: true });
870
+ await writeFile(ledgerPath, "", "utf8");
871
+ const pending = plaintextPendingIo();
872
+ // A claim orphaned by a crashed prior drain, plus a brand-new append: the
873
+ // append path must recover the orphan AND write the new event, losing neither.
874
+ await writeFile(
875
+ path.join(spillDir, "orphan.jsonl.claimed"),
876
+ `${JSON.stringify(lifecycleEvent("evt-orphan", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
877
+ "utf8",
878
+ );
879
+
880
+ await appendLifecycleEventsSerialized(
881
+ ledgerPath,
882
+ async (payload) => { await appendFile(ledgerPath, payload, "utf8"); },
883
+ `${JSON.stringify(lifecycleEvent("evt-new", "memory-b", "2026-03-08T01:00:00.000Z"))}\n`,
884
+ pending,
885
+ );
886
+
887
+ const ledger = await readFile(ledgerPath, "utf8");
888
+ assert.ok(ledger.includes("evt-orphan"), "crash-orphaned claim recovered into the ledger");
889
+ assert.ok(ledger.includes("evt-new"), "new event appended");
890
+ assert.equal((await readdir(spillDir)).length, 0, "spill dir drained empty");
891
+ } finally {
892
+ await rm(dir, { recursive: true, force: true });
893
+ }
894
+ });
895
+
896
+ test("appendLifecycleEventsSerialized refuses to write a spill into a symlinked pending directory (#2033)", async () => {
897
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-symlink-dir-"));
898
+ const outsideDir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-spill-outside-dir-"));
899
+ try {
900
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
901
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
902
+ await writeFile(ledgerPath, "", "utf8");
903
+ // Plant a symlink AT the spill-directory path pointing at a real directory
904
+ // outside the memory store. The write path must refuse it before any file
905
+ // lands in the target.
906
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
907
+ await symlink(outsideDir, spillDir);
908
+
909
+ // Hold the ledger lock from a foreign owner so the append takes the spill
910
+ // branch instead of writing the ledger directly.
911
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
912
+ await writeFile(lockPath, `${process.pid} held-by-test ${new Date().toISOString()}\n`, "utf8");
913
+
914
+ await assert.rejects(
915
+ () =>
916
+ appendLifecycleEventsSerialized(
917
+ ledgerPath,
918
+ async () => { throw new Error("ledger append must not run"); },
919
+ `${JSON.stringify(lifecycleEvent("evt-symlink", "memory-a", "2026-03-08T00:00:00.000Z"))}\n`,
920
+ plaintextPendingIo(),
921
+ { maxWaitMs: 100, pollMs: 20 },
922
+ ),
923
+ /symlinked or non-directory/,
924
+ "spill write into a symlinked pending dir must be refused",
925
+ );
926
+
927
+ assert.equal(
928
+ (await lstat(spillDir)).isSymbolicLink(),
929
+ true,
930
+ "spill directory symlink left intact (not replaced by a real dir)",
931
+ );
932
+ assert.deepEqual(
933
+ await readdir(outsideDir),
934
+ [],
935
+ "no spill file leaked through the symlink into the outside directory",
936
+ );
937
+ } finally {
938
+ await rm(dir, { recursive: true, force: true });
939
+ await rm(outsideDir, { recursive: true, force: true });
940
+ }
941
+ });
942
+
943
+ test("rebuild aborts the compaction rewrite when a peer stale-breaks the ledger lock mid-section (#1910/#2033)", async () => {
944
+ // The preserve+merge+serialize inside the lock is CPU-bound and blocks the
945
+ // timer heartbeat, so within the 30s stale window a peer can judge the lock
946
+ // stale, break it, and hold its own. The rewrite MUST re-assert ownership
947
+ // right before its destructive write and ABORT — not rename its compacted
948
+ // file over the peer's append. We drive the peer break deterministically as a
949
+ // side effect of the under-lock preserve read.
950
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-rebuild-lock-lost-"));
951
+ try {
952
+ await mkdir(path.join(memoryDir, "facts", "2026-03-08"), { recursive: true });
953
+ await writeFile(
954
+ path.join(memoryDir, "facts", "2026-03-08", "fact-1.md"),
955
+ `---\nid: fact-1\ncategory: fact\ncreated: 2026-03-08T00:00:00.000Z\n`
956
+ + `updated: 2026-03-08T01:00:00.000Z\nsource: test\nconfidence: 0.8\n`
957
+ + `confidenceTier: implied\ntags: ["alpha"]\n---\n\nalpha\n`,
958
+ "utf8",
959
+ );
960
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
961
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
962
+ const originalRow =
963
+ `${JSON.stringify(lifecycleEvent("evt-original", "fact-1", "2026-03-08T00:30:00.000Z"))}\n`;
964
+ await writeFile(ledgerPath, originalRow, "utf8");
965
+
966
+ const lockPath = memoryLifecycleLedgerLockPath(ledgerPath);
967
+ const foreignOwner = "00000000-0000-4000-8000-000000000000";
968
+ const storage = new StorageManager(memoryDir);
969
+ // Simulate a peer stale-breaking our lock DURING the under-lock preserve
970
+ // read: overwrite the lock content with a foreign owner id (a peer that now
971
+ // holds it). The rewrite's pre-write ownership re-check must then see the
972
+ // lock is no longer ours and abort.
973
+ const origRead = storage.readAllMemoryLifecycleEventsForCompaction.bind(storage);
974
+ let broken = false;
975
+ storage.readAllMemoryLifecycleEventsForCompaction = async () => {
976
+ const events = await origRead();
977
+ if (!broken) {
978
+ broken = true;
979
+ await writeFile(lockPath, `999999 ${foreignOwner} ${new Date().toISOString()}\n`, "utf8");
980
+ }
981
+ return events;
982
+ };
983
+
984
+ await assert.rejects(
985
+ rebuildMemoryLifecycleLedger({
986
+ memoryDir,
987
+ dryRun: false,
988
+ storage,
989
+ preserveExistingEvents: true,
990
+ }),
991
+ /lost the ledger lock during the compaction rewrite/,
992
+ "rewrite must abort rather than clobber a peer that stale-broke the lock",
993
+ );
994
+
995
+ assert.match(
996
+ await readFile(lockPath, "utf8"),
997
+ new RegExp(foreignOwner),
998
+ "foreign (peer) lock left intact — the aborted rewrite never released it",
999
+ );
1000
+ assert.equal(
1001
+ await readFile(ledgerPath, "utf8"),
1002
+ originalRow,
1003
+ "active ledger not clobbered by the aborted rewrite",
1004
+ );
1005
+ } finally {
1006
+ await rm(memoryDir, { recursive: true, force: true });
1007
+ }
1008
+ });
1009
+
1010
+ test("encrypted rebuild backup retains raw malformed/truncated/future rows verbatim while the active ledger normalizes (#2033)", async () => {
1011
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-rebuild-raw-backup-"));
1012
+ try {
1013
+ const key = Buffer.alloc(32, 9);
1014
+ const storage = new StorageManager(memoryDir);
1015
+ storage.setSecureStoreKey(key);
1016
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
1017
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
1018
+
1019
+ // Seed an encrypted-at-rest ledger that mixes valid rows with rows the
1020
+ // fail-open parser silently drops: a non-JSON line, a truncated JSON line,
1021
+ // and a valid-but-future-schema row carrying an unknown field. The backup
1022
+ // must keep every byte; the compacted active ledger may drop the garbage.
1023
+ const validA = JSON.stringify(lifecycleEvent("evt-a", "memory-a", "2026-01-01T00:00:00.000Z"));
1024
+ const validB = JSON.stringify(lifecycleEvent("evt-b", "memory-b", "2026-01-02T00:00:00.000Z"));
1025
+ const malformed = "this-is-not-json-at-all";
1026
+ const truncated = '{"eventId":"evt-broken","memoryId":"memory-c",';
1027
+ const future = JSON.stringify({
1028
+ ...lifecycleEvent("evt-future", "memory-d", "9999-12-31T23:59:59.000Z"),
1029
+ unknownFutureField: { schema: 42 },
1030
+ });
1031
+ const rawContent = `${validA}\n${malformed}\n${truncated}\n${validB}\n${future}\n`;
1032
+ await writeFile(ledgerPath, encryptFileBody(rawContent, key, filePathAad(ledgerPath, memoryDir)));
1033
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "precondition: ledger encrypted at rest");
1034
+
1035
+ const result = await rebuildMemoryLifecycleLedger({
1036
+ memoryDir,
1037
+ dryRun: false,
1038
+ storage,
1039
+ preserveExistingEvents: true,
1040
+ });
1041
+
1042
+ // A timestamped encrypted backup was written.
1043
+ assert.ok(result.backupPath, "a backup path was produced");
1044
+ assert.match(result.backupPath!, /archive[/\\]memory-lifecycle-ledger[/\\]/);
1045
+ assert.ok(isEncryptedFile(await readFile(result.backupPath!)), "backup encrypted at rest");
1046
+
1047
+ // The backup, decrypted under its OWN path AAD, is byte-for-byte the raw
1048
+ // pre-compaction ledger: no reserialization, no dropped garbage rows.
1049
+ const backupDecrypted = await readMaybeEncryptedFile(result.backupPath!, key, memoryDir);
1050
+ assert.equal(backupDecrypted, rawContent, "backup preserves raw decrypted bytes verbatim");
1051
+ for (const raw of [malformed, truncated, future]) {
1052
+ assert.ok(backupDecrypted.includes(raw), `backup retains raw row: ${raw}`);
1053
+ }
1054
+
1055
+ // The active ledger normalized: the garbage rows are gone, the valid rows
1056
+ // survive, and its decrypted content is NOT the raw seed.
1057
+ const activeDecrypted = await readMaybeEncryptedFile(ledgerPath, key, memoryDir);
1058
+ assert.notEqual(activeDecrypted, rawContent, "active ledger was compacted, not copied");
1059
+ assert.ok(!activeDecrypted.includes(malformed), "active ledger dropped the non-JSON row");
1060
+ assert.ok(!activeDecrypted.includes(truncated), "active ledger dropped the truncated row");
1061
+ const activeIds = (await storage.readAllMemoryLifecycleEvents()).map((e) => e.eventId);
1062
+ assert.ok(activeIds.includes("evt-a") && activeIds.includes("evt-b"), "valid rows preserved in active ledger");
1063
+ assert.ok(!activeIds.includes("evt-broken"), "malformed row absent from active ledger");
1064
+ } finally {
1065
+ await rm(memoryDir, { recursive: true, force: true });
1066
+ }
1067
+ });
1068
+
1069
+ test("encrypted rebuild forces encryption for backup + active ledger even when encrypt-on-write is paused (#2033)", async () => {
1070
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-rebuild-force-encrypt-"));
1071
+ try {
1072
+ const key = Buffer.alloc(32, 11);
1073
+ // Store is UNLOCKED (key present) but the encrypt-on-write policy is paused.
1074
+ // A compaction of an already-encrypted ledger must still preserve encryption
1075
+ // at rest — a plaintext rewrite would silently downgrade encrypted state.
1076
+ const storage = new StorageManager(memoryDir);
1077
+ storage.setSecureStoreKey(key, false);
1078
+ assert.equal(storage.willEncryptStateWrites(), false, "precondition: encrypt-on-write paused");
1079
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
1080
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
1081
+
1082
+ // Seed an encrypted-at-rest ledger with two valid rows plus a garbage row so
1083
+ // the compaction produces a DIFFERENT active ledger (no no-op skip) and a
1084
+ // timestamped backup.
1085
+ const validA = JSON.stringify(lifecycleEvent("evt-a", "memory-a", "2026-01-01T00:00:00.000Z"));
1086
+ const validB = JSON.stringify(lifecycleEvent("evt-b", "memory-b", "2026-01-02T00:00:00.000Z"));
1087
+ const rawContent = `${validA}\nnot-json-garbage\n${validB}\n`;
1088
+ await writeFile(ledgerPath, encryptFileBody(rawContent, key, filePathAad(ledgerPath, memoryDir)));
1089
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "precondition: ledger encrypted at rest");
1090
+
1091
+ const result = await rebuildMemoryLifecycleLedger({
1092
+ memoryDir,
1093
+ dryRun: false,
1094
+ storage,
1095
+ preserveExistingEvents: true,
1096
+ });
1097
+
1098
+ // The active ledger stays encrypted at rest and remains decryptable — no
1099
+ // plaintext downgrade despite the paused encrypt-on-write policy.
1100
+ assert.ok(result.rewritten, "a real rewrite happened");
1101
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "active ledger stays encrypted at rest");
1102
+ const activeDecrypted = await readMaybeEncryptedFile(ledgerPath, key, memoryDir);
1103
+ assert.ok(!activeDecrypted.includes("not-json-garbage"), "active ledger compacted the garbage row");
1104
+
1105
+ // The backup is encrypted at rest too and decrypts to the raw pre-compaction
1106
+ // bytes under its own path AAD.
1107
+ assert.ok(result.backupPath, "a backup path was produced");
1108
+ assert.ok(isEncryptedFile(await readFile(result.backupPath!)), "backup stays encrypted at rest");
1109
+ const backupDecrypted = await readMaybeEncryptedFile(result.backupPath!, key, memoryDir);
1110
+ assert.equal(backupDecrypted, rawContent, "backup preserves raw decrypted bytes verbatim");
1111
+ } finally {
1112
+ await rm(memoryDir, { recursive: true, force: true });
1113
+ }
1114
+ });
1115
+
1116
+ test("encrypted rebuild backup never materializes the raw ledger as one giant string and stays byte-identical (#2033)", async () => {
1117
+ // Regression for the P2 raw-backup thread: the secure-store backup path once
1118
+ // read the decrypted ledger via `Buffer.toString("utf8")`. For a ledger that
1119
+ // already grew past V8's ~512MB string ceiling — the exact corruption this PR
1120
+ // recovers from — that throw aborted BOTH auto-compaction and
1121
+ // `rebuild-memory-lifecycle-ledger --write`, leaving the oversized ledger
1122
+ // unrepairable. The raw bytes must now flow Buffer -> re-encrypt without a
1123
+ // whole-buffer decode. We prove it deterministically (no 512MB allocation) by
1124
+ // trapping any whole-buffer utf8 decode above a small cap while the rebuild
1125
+ // runs; a single such decode fails the test.
1126
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-rebuild-raw-nostring-"));
1127
+ const originalToString = Buffer.prototype.toString;
1128
+ try {
1129
+ const key = Buffer.alloc(32, 7);
1130
+ const storage = new StorageManager(memoryDir);
1131
+ storage.setSecureStoreKey(key);
1132
+ const ledgerPath = path.join(memoryDir, "state", "memory-lifecycle-ledger.jsonl");
1133
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
1134
+
1135
+ // A ~2MB encrypted ledger: two valid rows plus many non-JSON garbage lines
1136
+ // the fail-open parser drops. The active rewrite stays tiny (two rows) while
1137
+ // the RAW backup must retain every byte.
1138
+ const validA = JSON.stringify(lifecycleEvent("evt-a", "memory-a", "2026-01-01T00:00:00.000Z"));
1139
+ const validB = JSON.stringify(lifecycleEvent("evt-b", "memory-b", "2026-01-02T00:00:00.000Z"));
1140
+ const garbageLine = `not-json-${"x".repeat(200)}`;
1141
+ const garbage = `${garbageLine}\n`.repeat(10_000); // ~2MB of undroppable-verbatim noise
1142
+ const rawContent = `${validA}\n${garbage}${validB}\n`;
1143
+ const rawBytes = Buffer.from(rawContent, "utf8");
1144
+ assert.ok(rawBytes.byteLength > 2_000_000, "precondition: raw ledger is multi-megabyte");
1145
+ await writeFile(ledgerPath, encryptFileBody(rawContent, key, filePathAad(ledgerPath, memoryDir)));
1146
+ assert.ok(isEncryptedFile(await readFile(ledgerPath)), "precondition: ledger encrypted at rest");
1147
+
1148
+ // Trap ONLY a whole-buffer utf8 decode larger than the cap. Ranged decodes
1149
+ // (the per-line ledger reader) produce small strings and pass, exactly as
1150
+ // V8's real "Invalid string length" limit fires on the result length — not
1151
+ // the receiver's size. If the raw-backup path still stringified the whole
1152
+ // decrypted buffer, this throws and the rebuild rejects.
1153
+ const STRING_CAP = 1_000_000;
1154
+ let trippedGiantDecode = false;
1155
+ Buffer.prototype.toString = function patchedToString(this: Buffer, ...args: unknown[]): string {
1156
+ const encoding = args[0];
1157
+ const start = typeof args[1] === "number" ? args[1] : 0;
1158
+ const end = typeof args[2] === "number" ? args[2] : this.length;
1159
+ const span = Math.max(0, end - start);
1160
+ const isUtf8 = encoding === undefined || /^utf-?8$/i.test(String(encoding));
1161
+ if (isUtf8 && span > STRING_CAP) {
1162
+ trippedGiantDecode = true;
1163
+ throw new Error(
1164
+ "Cannot create a string longer than 0x1fffffe8 characters (simulated V8 string cap)",
1165
+ );
1166
+ }
1167
+ return originalToString.apply(this, args as never) as string;
1168
+ } as typeof Buffer.prototype.toString;
1169
+
1170
+ let result: RebuildMemoryLifecycleLedgerResult;
1171
+ try {
1172
+ result = await rebuildMemoryLifecycleLedger({
1173
+ memoryDir,
1174
+ dryRun: false,
1175
+ storage,
1176
+ preserveExistingEvents: true,
1177
+ });
1178
+ } finally {
1179
+ Buffer.prototype.toString = originalToString;
1180
+ }
1181
+
1182
+ assert.equal(
1183
+ trippedGiantDecode,
1184
+ false,
1185
+ "rebuild must never decode the whole raw ledger buffer to a string",
1186
+ );
1187
+
1188
+ // The backup, decrypted under its OWN path AAD, is byte-for-byte the raw
1189
+ // pre-compaction ledger — compared as buffers to avoid materializing a giant
1190
+ // string in the assertion itself.
1191
+ assert.ok(result.backupPath, "a backup path was produced");
1192
+ assert.ok(isEncryptedFile(await readFile(result.backupPath!)), "backup encrypted at rest");
1193
+ const backupBytes = await readMaybeEncryptedFileBuffer(result.backupPath!, key, memoryDir);
1194
+ assert.equal(Buffer.compare(backupBytes, rawBytes), 0, "backup preserves raw decrypted bytes verbatim");
1195
+
1196
+ // The active ledger normalized down to the two valid rows.
1197
+ const activeIds = (await storage.readAllMemoryLifecycleEvents()).map((e) => e.eventId);
1198
+ assert.deepEqual(activeIds.sort(), ["evt-a", "evt-b"], "active ledger keeps only the valid rows");
1199
+ } finally {
1200
+ Buffer.prototype.toString = originalToString;
1201
+ await rm(memoryDir, { recursive: true, force: true });
1202
+ }
1203
+ });
1204
+
1205
+ test("pending drain reads spills lazily per batch, not all up front — memory-bounded (#2033)", async () => {
1206
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-append-drain-lazy-"));
1207
+ try {
1208
+ const ledgerPath = path.join(dir, "state", "memory-lifecycle-ledger.jsonl");
1209
+ await mkdir(path.dirname(ledgerPath), { recursive: true });
1210
+ await writeFile(ledgerPath, "", "utf8");
1211
+ const spillDir = pendingLifecycleLedgerDir(ledgerPath);
1212
+ await mkdir(spillDir, { recursive: true });
1213
+ // Four ~400KB spills. Bounded 1MB batches commit two per append, so with a
1214
+ // LAZY per-batch read the second batch's reads happen AFTER the first
1215
+ // batch's append. An eager collect-all would read EVERY spill before any
1216
+ // append, holding the whole queue in memory at once (the memory the bound
1217
+ // exists to cap, #2033).
1218
+ const chunk = "z".repeat(400 * 1024);
1219
+ for (const name of ["a", "b", "c", "d"]) {
1220
+ await writeFile(path.join(spillDir, `${name}.jsonl`), `${name}:${chunk}\n`, "utf8");
1221
+ }
1222
+ const events: string[] = [];
1223
+ const io: LifecyclePendingIo = {
1224
+ writeSecure: async () => { throw new Error("writeSecure unused in this drain test"); },
1225
+ readSecure: async (p) => {
1226
+ events.push("read");
1227
+ return readFile(p, "utf8");
1228
+ },
1229
+ };
1230
+ const drained = await drainPendingLifecycleAppendsSerialized(
1231
+ ledgerPath,
1232
+ async (payload) => { events.push("append"); await appendFile(ledgerPath, payload, "utf8"); },
1233
+ io,
1234
+ );
1235
+ assert.equal(drained, true, "rows drained");
1236
+ assert.ok(events.includes("append"), "at least one append happened");
1237
+ assert.ok(
1238
+ events.indexOf("append") < events.lastIndexOf("read"),
1239
+ `a spill was read AFTER an append (lazy per-batch read), not all up front: ${events.join(",")}`,
1240
+ );
1241
+ const ledger = await readFile(ledgerPath, "utf8");
1242
+ for (const name of ["a", "b", "c", "d"]) {
1243
+ assert.ok(ledger.includes(`${name}:`), `${name} folded into the ledger`);
1244
+ }
1245
+ assert.equal((await readdir(spillDir)).length, 0, "all spills drained");
1246
+ } finally {
1247
+ await rm(dir, { recursive: true, force: true });
1248
+ }
1249
+ });
1250
+
1251
+ test("compaction buffer reader skips an oversized unterminated row instead of throwing (#2033)", async () => {
1252
+ const validA = lifecycleEvent("evt-a", "memory-a", "2026-01-01T00:00:00.000Z");
1253
+ const validB = lifecycleEvent("evt-b", "memory-b", "2026-01-02T00:00:00.000Z");
1254
+ // A single malformed row far larger than the per-row decode cap (64MB) with no
1255
+ // newline: decoding it whole as one string could exceed V8's string limit and
1256
+ // THROW before the fail-open parser can drop it, aborting a compaction that
1257
+ // could otherwise rewrite the ledger from its good rows. It MUST be skipped
1258
+ // undecoded; the valid rows on either side still parse.
1259
+ const oversized = Buffer.alloc(64 * 1024 * 1024 + 1, 0x41); // 'A' * (cap+1), no newline
1260
+ const buf = Buffer.concat([
1261
+ oversized,
1262
+ Buffer.from(`\n${JSON.stringify(validA)}\n${JSON.stringify(validB)}\n`, "utf8"),
1263
+ ]);
1264
+ const events = await readAllLifecycleEventsFromLedgerBuffer(
1265
+ path.join("state", "memory-lifecycle-ledger.jsonl"),
1266
+ async () => buf,
1267
+ );
1268
+ assert.deepEqual(
1269
+ events.map((e) => e.eventId),
1270
+ ["evt-a", "evt-b"],
1271
+ "valid rows returned; the oversized row is skipped without throwing",
1272
+ );
1273
+ });