@remnic/core 10.0.83 → 10.0.85

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 (413) hide show
  1. package/dist/access-admin-ops-surface.js +67 -66
  2. package/dist/access-authorization-probe.js +68 -67
  3. package/dist/access-boundary.js +67 -66
  4. package/dist/access-cli.js +148 -146
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +67 -66
  7. package/dist/access-http-briefing.js +68 -67
  8. package/dist/access-http-query.js +69 -68
  9. package/dist/access-http.js +91 -89
  10. package/dist/access-identity-continuity-surface.js +67 -66
  11. package/dist/access-lcm-surface.js +67 -66
  12. package/dist/access-mcp-frames.d.ts +29 -0
  13. package/dist/access-mcp-frames.d.ts.map +1 -0
  14. package/dist/access-mcp-frames.js +10 -0
  15. package/dist/access-mcp.d.ts +1 -1
  16. package/dist/access-mcp.d.ts.map +1 -1
  17. package/dist/access-mcp.js +82 -80
  18. package/dist/access-namespace-preflight.js +67 -66
  19. package/dist/access-observe-write-category.js +9 -9
  20. package/dist/access-observe-write-surface.d.ts.map +1 -1
  21. package/dist/access-observe-write-surface.js +67 -66
  22. package/dist/access-offline-manifest.js +3 -3
  23. package/dist/access-operations-batch.js +71 -70
  24. package/dist/access-operations.d.ts +1 -1
  25. package/dist/access-operations.js +78 -77
  26. package/dist/access-recall-concurrency.js +67 -66
  27. package/dist/access-recall-response.js +67 -66
  28. package/dist/access-recall-surface.js +67 -66
  29. package/dist/access-schema.js +4 -4
  30. package/dist/access-service.js +67 -66
  31. package/dist/access-surface-catalog.js +69 -68
  32. package/dist/access-surface-catalog.js.map +1 -1
  33. package/dist/active-recall.js +31 -31
  34. package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
  35. package/dist/briefing.js +25 -25
  36. package/dist/buffer-persistence.d.ts +8 -4
  37. package/dist/buffer-persistence.d.ts.map +1 -1
  38. package/dist/buffer-persistence.js +1 -1
  39. package/dist/buffer-turn-helpers.d.ts +5 -0
  40. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  41. package/dist/buffer-turn-helpers.js +1 -1
  42. package/dist/buffer.d.ts +4 -1
  43. package/dist/buffer.d.ts.map +1 -1
  44. package/dist/buffer.js +9 -9
  45. package/dist/calibration.js +2 -2
  46. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
  47. package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
  48. package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
  49. package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
  50. package/dist/causal-behavior.js +2 -2
  51. package/dist/causal-chain.js +2 -2
  52. package/dist/causal-consolidation.js +32 -32
  53. package/dist/causal-retrieval.js +2 -2
  54. package/dist/causal-trajectory-graph.js +2 -2
  55. package/dist/causal-trajectory.js +1 -1
  56. package/dist/{chunk-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
  57. package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
  58. package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
  59. package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
  60. package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
  61. package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
  62. package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
  63. package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
  64. package/dist/chunk-3ODZLCHC.js.map +1 -0
  65. package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
  66. package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
  67. package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
  68. package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
  69. package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
  70. package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
  71. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  72. package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
  73. package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
  74. package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
  75. package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
  76. package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
  77. package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
  78. package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
  79. package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
  80. package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
  81. package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
  82. package/dist/chunk-BMIM6ESC.js.map +1 -0
  83. package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
  84. package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
  85. package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
  86. package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
  87. package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
  88. package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
  89. package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
  90. package/dist/{chunk-S57QBPTA.js → chunk-DPPLNZ3A.js} +4 -6
  91. package/dist/chunk-DPPLNZ3A.js.map +1 -0
  92. package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
  93. package/dist/{chunk-DD6OIFDG.js → chunk-E7V5AK7I.js} +3 -3
  94. package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
  95. package/dist/chunk-ELWI2R6H.js.map +1 -0
  96. package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
  97. package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
  98. package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
  99. package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
  100. package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
  101. package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
  102. package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
  103. package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
  104. package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
  105. package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
  106. package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
  107. package/dist/chunk-H34INI3P.js.map +1 -0
  108. package/dist/{chunk-AYTDCMEL.js → chunk-H4D5YURJ.js} +80 -94
  109. package/dist/chunk-H4D5YURJ.js.map +1 -0
  110. package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
  111. package/dist/chunk-HVFX2N2G.js.map +1 -0
  112. package/dist/chunk-IGLJNHOX.js +62 -0
  113. package/dist/chunk-IGLJNHOX.js.map +1 -0
  114. package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
  115. package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
  116. package/dist/chunk-JFH6JAGY.js.map +1 -0
  117. package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
  118. package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
  119. package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
  120. package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
  121. package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
  122. package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
  123. package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
  124. package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
  125. package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
  126. package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
  127. package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
  128. package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
  129. package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
  130. package/dist/{chunk-SVCHO337.js → chunk-ONN2E5QD.js} +32 -23
  131. package/dist/chunk-ONN2E5QD.js.map +1 -0
  132. package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
  133. package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
  134. package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
  135. package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
  136. package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
  137. package/dist/chunk-QV2VZKD5.js +31 -0
  138. package/dist/chunk-QV2VZKD5.js.map +1 -0
  139. package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
  140. package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
  141. package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
  142. package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
  143. package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
  144. package/dist/chunk-SGHV6LZ7.js.map +1 -0
  145. package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
  146. package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
  147. package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
  148. package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
  149. package/dist/chunk-UDKWFDV2.js.map +1 -0
  150. package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
  151. package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
  152. package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
  153. package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
  154. package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
  155. package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
  156. package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
  157. package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
  158. package/dist/chunk-WLNWFHX3.js.map +1 -0
  159. package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
  160. package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
  161. package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
  162. package/dist/chunk-XO23OKMK.js.map +1 -0
  163. package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
  164. package/dist/chunk-XSFQN7TP.js.map +1 -0
  165. package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
  166. package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
  167. package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
  168. package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
  169. package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
  170. package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
  171. package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
  172. package/dist/cli.js +110 -108
  173. package/dist/coding/export-okf-codegraph.js +25 -25
  174. package/dist/coding/pre-action-gate.js +2 -2
  175. package/dist/compounding/engine.js +27 -27
  176. package/dist/config.js +28 -28
  177. package/dist/connectors/codex-materialize-runner.js +25 -25
  178. package/dist/connectors/index.js +25 -25
  179. package/dist/contradiction/index.js +4 -4
  180. package/dist/convergence-refresh.js +27 -27
  181. package/dist/conversation-index/backend.js +2 -2
  182. package/dist/corpus-watermark.js +27 -27
  183. package/dist/deep-recall-http-glue.js +5 -5
  184. package/dist/entity-mention-index-cache.d.ts.map +1 -1
  185. package/dist/entity-mention-index-cache.js +2 -1
  186. package/dist/entity-retrieval.js +27 -26
  187. package/dist/event-order-recall.js +2 -2
  188. package/dist/explicit-capture.js +5 -5
  189. package/dist/external-wiki-access.js +68 -67
  190. package/dist/extraction-evidence-class.js +2 -2
  191. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  192. package/dist/extraction-quote-grounding.js +1 -1
  193. package/dist/extraction-source-grounding-rules.js +2 -2
  194. package/dist/extraction-source-grounding.js +5 -5
  195. package/dist/extraction-span-materialize.js +2 -2
  196. package/dist/extraction.js +40 -40
  197. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  198. package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
  199. package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
  200. package/dist/graph-jsonl.js +3 -3
  201. package/dist/graph-snapshot.js +3 -3
  202. package/dist/graph.js +2 -2
  203. package/dist/harmonic-retrieval.js +25 -25
  204. package/dist/index.js +205 -203
  205. package/dist/lcm/engine.js +2 -2
  206. package/dist/lcm/index.js +2 -2
  207. package/dist/local-llm.js +2 -2
  208. package/dist/location/index.js +28 -28
  209. package/dist/maintenance/memory-governance.js +25 -25
  210. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
  211. package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
  212. package/dist/maintenance/rebuild-memory-projection.js +26 -26
  213. package/dist/memory-browse-operations.js +68 -67
  214. package/dist/memory-subject.js +2 -2
  215. package/dist/namespaces/migrate.js +26 -26
  216. package/dist/namespaces/search.d.ts +8 -0
  217. package/dist/namespaces/search.d.ts.map +1 -1
  218. package/dist/namespaces/search.js +10 -6
  219. package/dist/namespaces/storage.js +25 -25
  220. package/dist/operator-doctor-corpus.js +28 -28
  221. package/dist/operator-toolkit.js +39 -39
  222. package/dist/orchestration/backlog-scheduler.d.ts +3 -2
  223. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  224. package/dist/orchestration/backlog-scheduler.js +159 -157
  225. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  226. package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
  227. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  228. package/dist/orchestration/buffer-drain-runner.js +149 -147
  229. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  230. package/dist/orchestration/fact-dedup-window.d.ts +1 -4
  231. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  232. package/dist/orchestration/maintenance.js +39 -39
  233. package/dist/orchestrator.js +148 -146
  234. package/dist/provenance.js +2 -2
  235. package/dist/recall-navigation-operations.js +68 -67
  236. package/dist/recall-navigation.js +67 -66
  237. package/dist/reconcile/manifest.js +2 -2
  238. package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
  239. package/dist/resolution-23ZBDM6A.js.map +1 -0
  240. package/dist/resume-bundles.js +29 -29
  241. package/dist/salvage-envelope.js +4 -4
  242. package/dist/search/factory.js +5 -5
  243. package/dist/search/index.js +9 -9
  244. package/dist/semantic-consolidation.js +26 -26
  245. package/dist/semantic-rule-promotion.js +25 -25
  246. package/dist/semantic-rule-verifier.js +25 -25
  247. package/dist/source-agent-qualifier.js +31 -31
  248. package/dist/standup/build.js +25 -25
  249. package/dist/storage/buffer-drain-claims.d.ts +18 -6
  250. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  251. package/dist/storage/buffer-drain-claims.js +2 -2
  252. package/dist/storage/buffer-session-files.js +2 -2
  253. package/dist/storage/content-hash-index.d.ts +8 -0
  254. package/dist/storage/content-hash-index.d.ts.map +1 -1
  255. package/dist/storage.d.ts +10 -1
  256. package/dist/storage.d.ts.map +1 -1
  257. package/dist/storage.js +24 -24
  258. package/dist/subject-config.js +3 -3
  259. package/dist/summarizer.js +3 -3
  260. package/dist/support-passport/index.js +70 -69
  261. package/dist/transfer/autodetect.js +1 -1
  262. package/dist/transfer/backup.js +2 -2
  263. package/dist/transfer/capsule-export.js +3 -3
  264. package/dist/transfer/capsule-gunzip.d.ts +14 -0
  265. package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
  266. package/dist/transfer/capsule-import.d.ts.map +1 -1
  267. package/dist/transfer/capsule-import.js +3 -2
  268. package/dist/transfer/capsule-merge.d.ts.map +1 -1
  269. package/dist/transfer/export-okf.js +25 -25
  270. package/dist/verified-recall.js +25 -25
  271. package/dist/who-knows-cli.js +26 -26
  272. package/dist/who-knows.js +25 -25
  273. package/dist/write-envelope.js +3 -3
  274. package/package.json +2 -2
  275. package/src/access-mcp-correction-tools.test.ts +45 -0
  276. package/src/access-mcp-frames.test.ts +47 -0
  277. package/src/access-mcp-frames.ts +74 -0
  278. package/src/access-mcp.test.ts +146 -0
  279. package/src/access-mcp.ts +30 -30
  280. package/src/access-observe-write-surface.ts +4 -4
  281. package/src/backlog-scheduler.test.ts +443 -3
  282. package/src/buffer-persistence.ts +15 -6
  283. package/src/buffer-turn-helpers.ts +6 -0
  284. package/src/buffer.ts +6 -6
  285. package/src/cli.ts +4 -4
  286. package/src/compounding/weekly-evidence.test.ts +237 -0
  287. package/src/entity-mention-index-cache.ts +1 -6
  288. package/src/extraction-quote-grounding.test.ts +49 -0
  289. package/src/extraction-quote-grounding.ts +50 -12
  290. package/src/fact-dedup-window.test.ts +103 -20
  291. package/src/maintenance/rebuild-memory-projection-runner.test.ts +90 -0
  292. package/src/namespaces/search.test.ts +339 -0
  293. package/src/namespaces/search.ts +54 -1
  294. package/src/orchestration/backlog-scheduler.ts +13 -12
  295. package/src/orchestration/buffer-drain-runner.ts +40 -16
  296. package/src/orchestration/extraction-persist.ts +2 -10
  297. package/src/orchestration/fact-dedup-window.ts +5 -12
  298. package/src/orchestration/full-corpus-heap-guard.test.ts +52 -0
  299. package/src/projection-rebuild-config.test.ts +79 -0
  300. package/src/storage/buffer-drain-claims.test.ts +330 -6
  301. package/src/storage/buffer-drain-claims.ts +226 -80
  302. package/src/storage/buffer-session-files.ts +2 -2
  303. package/src/storage/content-hash-index.ts +9 -0
  304. package/src/storage/entity-store-symlink.test.ts +44 -0
  305. package/src/storage.ts +15 -0
  306. package/src/transfer/capsule-gunzip.ts +39 -0
  307. package/src/transfer/capsule-import.test.ts +73 -0
  308. package/src/transfer/capsule-import.ts +2 -2
  309. package/src/transfer/capsule-merge.test.ts +34 -0
  310. package/src/transfer/capsule-merge.ts +2 -10
  311. package/src/utils/malformed-entity-record.test.ts +69 -0
  312. package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
  313. package/dist/chunk-4KLQI3L2.js.map +0 -1
  314. package/dist/chunk-6LCWKS62.js.map +0 -1
  315. package/dist/chunk-7PMOCQOL.js.map +0 -1
  316. package/dist/chunk-AYTDCMEL.js.map +0 -1
  317. package/dist/chunk-B74NPUHT.js.map +0 -1
  318. package/dist/chunk-KQUSP6LB.js.map +0 -1
  319. package/dist/chunk-LU66I56Q.js.map +0 -1
  320. package/dist/chunk-MPDLLLOG.js.map +0 -1
  321. package/dist/chunk-S57QBPTA.js.map +0 -1
  322. package/dist/chunk-SVCHO337.js.map +0 -1
  323. package/dist/chunk-WAD32CZG.js.map +0 -1
  324. package/dist/chunk-XYP5WBDO.js.map +0 -1
  325. package/dist/chunk-YK7GOPIE.js.map +0 -1
  326. package/dist/chunk-Z3DDQXM4.js.map +0 -1
  327. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
  328. /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
  329. /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  330. /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
  331. /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
  332. /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
  333. /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
  334. /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
  335. /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
  336. /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
  337. /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
  338. /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
  339. /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
  340. /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
  341. /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
  342. /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
  343. /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
  344. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  345. /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
  346. /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
  347. /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
  348. /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
  349. /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
  350. /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
  351. /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
  352. /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
  353. /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
  354. /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
  355. /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
  356. /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
  357. /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
  358. /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
  359. /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
  360. /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
  361. /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
  362. /package/dist/{chunk-DD6OIFDG.js.map → chunk-E7V5AK7I.js.map} +0 -0
  363. /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
  364. /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
  365. /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
  366. /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
  367. /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
  368. /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
  369. /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
  370. /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
  371. /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
  372. /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
  373. /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
  374. /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
  375. /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
  376. /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
  377. /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
  378. /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
  379. /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
  380. /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
  381. /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
  382. /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
  383. /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
  384. /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
  385. /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
  386. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
  387. /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
  388. /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
  389. /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
  390. /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
  391. /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
  392. /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
  393. /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
  394. /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
  395. /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
  396. /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
  397. /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
  398. /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
  399. /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
  400. /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
  401. /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
  402. /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
  403. /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
  404. /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
  405. /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
  406. /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
  407. /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
  408. /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
  409. /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
  410. /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
  411. /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
  412. /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
  413. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -19,9 +19,6 @@ import { log } from "../logger.js";
19
19
  * OUTSIDE the window exactly like the consolidation LLM call stays outside
20
20
  * commitConsolidatedProfile.
21
21
  */
22
- export interface FactDedupProbe {
23
- hasContentHashDedup(storage: StorageManager, key: string): Promise<boolean>;
24
- }
25
22
 
26
23
  interface FactDedupMatchOptions {
27
24
  readonly category: string;
@@ -55,22 +52,18 @@ function matchesActiveConnectorDuplicate(memory: MemoryFile, opts: FactDedupMatc
55
52
  * silently drop content.
56
53
  */
57
54
  export async function confirmActiveFactDuplicate(
58
- probe: FactDedupProbe,
59
55
  storage: StorageManager,
60
56
  opts: FactDedupMatchOptions,
61
57
  ): Promise<boolean> {
62
- let needsCorpusConfirm: boolean;
63
58
  try {
64
- if (!(await probe.hasContentHashDedup(storage, opts.contentHashDedupKey))) {
65
- if (await storage.isFactContentHashAuthoritative()) return false;
66
- needsCorpusConfirm = true;
67
- } else {
68
- needsCorpusConfirm = true;
69
- }
59
+ // One lookup returns the MISS together with the authority behind it, so a
60
+ // stale MISS is never paired with authority from another moment: any
61
+ // second read of authority can rebuild the index and bless it (#3312).
62
+ const lookup = await storage.lookupContentHash(opts.contentHashDedupKey);
63
+ if (!lookup.present && lookup.authoritative) return false;
70
64
  } catch {
71
65
  // Dedup lookup failed (e.g. the authoritative rebuild could not run). Do
72
66
  // NOT fail-open into a possible duplicate — confirm against the corpus.
73
- needsCorpusConfirm = true;
74
67
  }
75
68
  try {
76
69
  // #2016 cold-tier finding: the authoritative content-hash rebuild unions
@@ -0,0 +1,52 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { fullCorpusHeapPressure } from "./full-corpus-heap-guard.js";
5
+
6
+ test("fullCorpusHeapPressure postpones only when usage is strictly above the limit", () => {
7
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 61, heap_size_limit: 100 }, 0.6), {
8
+ postpone: true,
9
+ ratio: 0.61,
10
+ });
11
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 60, heap_size_limit: 100 }, 0.6), {
12
+ postpone: false,
13
+ ratio: 0.6,
14
+ });
15
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 59, heap_size_limit: 100 }, 0.6), {
16
+ postpone: false,
17
+ ratio: 0.59,
18
+ });
19
+ });
20
+
21
+ test("fullCorpusHeapPressure never fires at limit ratio 1", () => {
22
+ const pressure = fullCorpusHeapPressure({ used_heap_size: 100, heap_size_limit: 100 }, 1);
23
+ assert.equal(pressure.postpone, false);
24
+ assert.equal(pressure.ratio, 1);
25
+ });
26
+
27
+ test("fullCorpusHeapPressure ignores unusable statistics or limits", () => {
28
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: 0 }, 0.6), {
29
+ postpone: false,
30
+ ratio: 0,
31
+ });
32
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: -1 }, 0.6), {
33
+ postpone: false,
34
+ ratio: 0,
35
+ });
36
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: Number.NaN, heap_size_limit: 100 }, 0.6), {
37
+ postpone: false,
38
+ ratio: 0,
39
+ });
40
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: -1, heap_size_limit: 100 }, 0.6), {
41
+ postpone: false,
42
+ ratio: 0,
43
+ });
44
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: 100 }, Number.NaN), {
45
+ postpone: false,
46
+ ratio: 0,
47
+ });
48
+ assert.deepEqual(
49
+ fullCorpusHeapPressure({ used_heap_size: Number.POSITIVE_INFINITY, heap_size_limit: 100 }, 0.6),
50
+ { postpone: false, ratio: 0 },
51
+ );
52
+ });
@@ -0,0 +1,79 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT,
6
+ parseProjectionRebuildHeapUsageLimit,
7
+ parseProjectionRebuildScheduleConfig,
8
+ } from "./projection-rebuild-config.js";
9
+
10
+ const parseIntegerAtLeast: Parameters<typeof parseProjectionRebuildScheduleConfig>[1] = (
11
+ value,
12
+ fallback,
13
+ min,
14
+ keyName,
15
+ ) => {
16
+ if (value === undefined || value === null) return fallback;
17
+ const n = Number(value);
18
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < min) {
19
+ throw new Error(`${keyName} must be an integer >= ${min}; got ${JSON.stringify(value)}`);
20
+ }
21
+ return n;
22
+ };
23
+
24
+ test("parseProjectionRebuildHeapUsageLimit defaults and accepts (0, 1]", () => {
25
+ assert.equal(parseProjectionRebuildHeapUsageLimit(undefined), DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT);
26
+ assert.equal(parseProjectionRebuildHeapUsageLimit(null), DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT);
27
+ assert.equal(DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT, 0.6);
28
+ assert.equal(parseProjectionRebuildHeapUsageLimit(0.5), 0.5);
29
+ assert.equal(parseProjectionRebuildHeapUsageLimit("0.75"), 0.75);
30
+ assert.equal(parseProjectionRebuildHeapUsageLimit(1), 1);
31
+ });
32
+
33
+ test("parseProjectionRebuildHeapUsageLimit rejects 0, negatives, and non-numbers", () => {
34
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(0), /projectionRebuildHeapUsageLimit/);
35
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(-0.1), /projectionRebuildHeapUsageLimit/);
36
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(1.01), /projectionRebuildHeapUsageLimit/);
37
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(Number.NaN), /projectionRebuildHeapUsageLimit/);
38
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit("abc"), /projectionRebuildHeapUsageLimit/);
39
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit({}), /projectionRebuildHeapUsageLimit/);
40
+ });
41
+
42
+ test("parseProjectionRebuildScheduleConfig defaults and coerces booleans", () => {
43
+ const defaults = parseProjectionRebuildScheduleConfig({}, parseIntegerAtLeast);
44
+ assert.equal(defaults.projectionRebuildEnabled, true);
45
+ assert.equal(defaults.projectionRebuildIntervalMs, 6 * 60 * 60 * 1000);
46
+ assert.equal(defaults.projectionRebuildHeapUsageLimit, 0.6);
47
+
48
+ const disabled = parseProjectionRebuildScheduleConfig(
49
+ { projectionRebuildEnabled: "false", projectionRebuildIntervalMs: 120_000, projectionRebuildHeapUsageLimit: 0.4 },
50
+ parseIntegerAtLeast,
51
+ );
52
+ assert.equal(disabled.projectionRebuildEnabled, false);
53
+ assert.equal(disabled.projectionRebuildIntervalMs, 120_000);
54
+ assert.equal(disabled.projectionRebuildHeapUsageLimit, 0.4);
55
+ });
56
+
57
+ test("parseProjectionRebuildScheduleConfig floors the interval via the integer parser", () => {
58
+ assert.throws(
59
+ () =>
60
+ parseProjectionRebuildScheduleConfig(
61
+ { projectionRebuildIntervalMs: 59_999 },
62
+ parseIntegerAtLeast,
63
+ ),
64
+ /projectionRebuildIntervalMs/,
65
+ );
66
+ assert.throws(
67
+ () =>
68
+ parseProjectionRebuildScheduleConfig(
69
+ { projectionRebuildIntervalMs: 90_000.5 },
70
+ parseIntegerAtLeast,
71
+ ),
72
+ /projectionRebuildIntervalMs/,
73
+ );
74
+ const ok = parseProjectionRebuildScheduleConfig(
75
+ { projectionRebuildIntervalMs: 60_000 },
76
+ parseIntegerAtLeast,
77
+ );
78
+ assert.equal(ok.projectionRebuildIntervalMs, 60_000);
79
+ });
@@ -5,10 +5,11 @@ import { hostname } from "node:os";
5
5
  import path from "node:path";
6
6
  import test from "node:test";
7
7
 
8
- import { BufferDrainClaims, type DrainLease } from "./buffer-drain-claims.js";
9
- import { bufferSessionFileName } from "./buffer-session-files.js";
8
+ import { BufferDrainClaims, type AckOutcome, type DrainLease } from "./buffer-drain-claims.js";
9
+ import { BufferSessionFiles, bufferSessionFileName } from "./buffer-session-files.js";
10
+ import { ClaimBufferStorage } from "../orchestration/buffer-drain-runner.js";
10
11
  import type { BufferEntryState } from "../types.js";
11
-
12
+ import { withHeldFileLock } from "../utils/serialize-mutations.js";
12
13
  /** Plain-file BufferFileIo double: claims logic is file-atomic, not crypto-aware. */
13
14
  function plainIo() {
14
15
  return {
@@ -25,6 +26,7 @@ function plainIo() {
25
26
  };
26
27
  }
27
28
 
29
+
28
30
  function turn(content: string, timestamp = "2026-11-01T00:00:00.000Z") {
29
31
  return { role: "user" as const, content, timestamp, sessionKey: "sess-1" };
30
32
  }
@@ -146,6 +148,43 @@ test("mergeIntoClaim appends unseen turns and dedupes turns already claimed", as
146
148
  }
147
149
  });
148
150
 
151
+ test("claim merge and reload preserve duplicate occurrence counts after partial extraction", async () => {
152
+ const s = setup();
153
+ try {
154
+ const same = turn("duplicate pending turn");
155
+ s.seedSession("sess-1", entry([same, same]));
156
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
157
+ const initial = await s.claims.load("sess-1");
158
+ assert.ok(initial);
159
+
160
+ await s.claims.writeEntry("sess-1", "worker-1", entry([same]), initial.entry);
161
+ assert.deepEqual(await s.claims.drainedTurns("sess-1"), { turns: [same], retainedTurns: [] });
162
+ await s.claims.mergeIntoClaim("sess-1", entry([same, same]));
163
+
164
+ const reloaded = new BufferDrainClaims(s.stateDir, plainIo());
165
+ assert.deepEqual((await reloaded.load("sess-1"))?.entry.turns, [same]);
166
+ assert.deepEqual(await reloaded.drainedTurns("sess-1"), { turns: [same], retainedTurns: [] });
167
+ } finally {
168
+ s.cleanup();
169
+ }
170
+ });
171
+
172
+ test("claim merge and reload retain every pending duplicate occurrence", async () => {
173
+ const s = setup();
174
+ try {
175
+ const initial = turn("initial pending turn");
176
+ const duplicate = turn("new duplicate", "2026-11-01T00:01:00.000Z");
177
+ s.seedSession("sess-1", entry([initial]));
178
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
179
+ await s.claims.mergeIntoClaim("sess-1", entry([initial, duplicate, duplicate]));
180
+
181
+ const reloaded = new BufferDrainClaims(s.stateDir, plainIo());
182
+ assert.deepEqual((await reloaded.load("sess-1"))?.entry.turns, [initial, duplicate, duplicate]);
183
+ } finally {
184
+ s.cleanup();
185
+ }
186
+ });
187
+
149
188
  test("mergeIntoClaim also dedupes retainedTurns", async () => {
150
189
  const s = setup();
151
190
  try {
@@ -160,6 +199,24 @@ test("mergeIntoClaim also dedupes retainedTurns", async () => {
160
199
  }
161
200
  });
162
201
 
202
+ test("mergeIntoClaim preserves retained work when the same identity drained from active turns", async () => {
203
+ const s = setup();
204
+ try {
205
+ const same = turn("source identity");
206
+ s.seedSession("sess-1", entry([same]));
207
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
208
+ const claimed = await s.claims.load("sess-1");
209
+ assert.ok(claimed);
210
+ await s.claims.writeEntry("sess-1", "worker-1", entry([]), claimed.entry);
211
+
212
+ await s.claims.mergeIntoClaim("sess-1", { ...entry([]), retainedTurns: [same] });
213
+
214
+ assert.deepEqual((await s.claims.load("sess-1"))?.entry.retainedTurns, [same]);
215
+ } finally {
216
+ s.cleanup();
217
+ }
218
+ });
219
+
163
220
  test("writeEntry replaces the claim content; ack keeps a tombstone the owner consumes", async () => {
164
221
  const s = setup();
165
222
  try {
@@ -167,7 +224,7 @@ test("writeEntry replaces the claim content; ack keeps a tombstone the owner con
167
224
  await s.claims.claimFromSessionFile("sess-1", makeLease());
168
225
  await s.claims.writeEntry("sess-1", "worker-1", entry([turn("b")]));
169
226
  assert.deepEqual((await s.claims.load("sess-1"))?.entry.turns.map((t) => t.content), ["b"]);
170
- assert.deepEqual((await s.claims.drainedTurns("sess-1")).map((t) => t.content), ["a"], "extracted turns are recorded");
227
+ assert.deepEqual((await s.claims.drainedTurns("sess-1")).turns.map((t) => t.content), ["a"], "extracted turns are recorded");
171
228
  assert.equal(await s.claims.ack("sess-1", "not-the-owner"), "busy");
172
229
  assert.equal(await s.claims.ack("sess-1", "worker-1"), "not_empty");
173
230
  await s.claims.writeEntry("sess-1", "worker-1", entry([]));
@@ -175,7 +232,7 @@ test("writeEntry replaces the claim content; ack keeps a tombstone the owner con
175
232
  // The claim file stays as a tombstone: a live owner with a stale copy
176
233
  // subtracts the drained turns instead of writing them back.
177
234
  assert.equal(s.claims.isClaimed("sess-1"), true);
178
- assert.deepEqual((await s.claims.drainedTurns("sess-1")).map((t) => t.content), ["a", "b"]);
235
+ assert.deepEqual((await s.claims.drainedTurns("sess-1")).turns.map((t) => t.content), ["a", "b"]);
179
236
  assert.equal(existsSync(s.claims.leasePathForTest("sess-1")), false, "the lease is released");
180
237
  await s.claims.release("sess-1");
181
238
  assert.equal(s.claims.isClaimed("sess-1"), false);
@@ -184,6 +241,145 @@ test("writeEntry replaces the claim content; ack keeps a tombstone the owner con
184
241
  }
185
242
  });
186
243
 
244
+ test("a tombstone subtracts only its original list when a stale entry has an identical retained copy", async () => {
245
+ const s = setup();
246
+ try {
247
+ const same = turn("one source identity");
248
+ s.seedSession("sess-1", entry([same]));
249
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
250
+ const claim = await s.claims.load("sess-1");
251
+ assert.ok(claim);
252
+ await s.claims.writeEntry("sess-1", "worker-1", entry([]), claim.entry);
253
+ assert.equal(await s.claims.ack("sess-1", "worker-1"), "acked");
254
+
255
+ const files = new BufferSessionFiles(s.stateDir, plainIo());
256
+ const staleLiveEntry: BufferEntryState = {
257
+ ...entry([same]),
258
+ retainedTurns: [same],
259
+ };
260
+ const outcome = await files.save("sess-1", staleLiveEntry);
261
+ assert.deepEqual(outcome.drainedTurns, { turns: [same], retainedTurns: [] });
262
+ const persisted = JSON.parse(readFileSync(s.sessionFile("sess-1"), "utf8"));
263
+ assert.deepEqual(persisted.turns, [], "the extracted active occurrence is removed");
264
+ assert.deepEqual(persisted.retainedTurns, [same], "an identical retained occurrence is not removed by active provenance");
265
+ } finally {
266
+ s.cleanup();
267
+ }
268
+ });
269
+
270
+ test("adopt rejects a reused owner id from a different process identity", async () => {
271
+ const s = setup();
272
+ try {
273
+ s.seedSession("sess-1", entry([turn("a")]));
274
+ const original = { ...makeLease("reused-owner"), pid: process.pid };
275
+ assert.equal(await s.claims.claimFromSessionFile("sess-1", original), "claimed");
276
+
277
+ const outcome = await s.claims.adopt("sess-1", {
278
+ ...makeLease("reused-owner"),
279
+ pid: process.pid + 1,
280
+ });
281
+
282
+ assert.equal(outcome, "busy");
283
+ assert.equal(JSON.parse(readFileSync(s.claims.leasePathForTest("sess-1"), "utf8")).pid, process.pid);
284
+ } finally {
285
+ s.cleanup();
286
+ }
287
+ });
288
+
289
+ test("adopt takes over an expired lease even when the owner id is reused by another process", async () => {
290
+ const s = setup();
291
+ try {
292
+ s.seedSession("sess-1", entry([turn("a")]));
293
+ const expired = { ...makeLease("reused-owner"), heartbeatAt: "2026-11-01T00:00:00.000Z" };
294
+ assert.equal(await s.claims.claimFromSessionFile("sess-1", expired), "claimed");
295
+
296
+ const outcome = await s.claims.adopt("sess-1", { ...makeLease("reused-owner"), pid: process.pid + 1 });
297
+
298
+ assert.equal(outcome, "adopted");
299
+ assert.equal(JSON.parse(readFileSync(s.claims.leasePathForTest("sess-1"), "utf8")).pid, process.pid + 1);
300
+ } finally {
301
+ s.cleanup();
302
+ }
303
+ });
304
+
305
+ test("reused owner IDs cannot authorize stale operations from another process", async () => {
306
+ const s = setup();
307
+ try {
308
+ s.seedSession("sess-1", entry([turn("pending")]));
309
+ assert.equal(await s.claims.claimFromSessionFile("sess-1", makeLease("reused-owner")), "claimed");
310
+ const claim = await s.claims.load("sess-1");
311
+ assert.ok(claim);
312
+ const replacement = { ...makeLease("reused-owner"), pid: process.pid + 1 };
313
+ writeFileSync(s.claims.leasePathForTest("sess-1"), JSON.stringify(replacement));
314
+
315
+ await assert.rejects(
316
+ s.claims.writeEntry("sess-1", "reused-owner", entry([]), claim.entry),
317
+ /no longer owned/,
318
+ );
319
+ assert.equal(await s.claims.heartbeat("sess-1", "reused-owner", 60_000), "lost");
320
+ assert.equal(await s.claims.ack("sess-1", "reused-owner"), "busy");
321
+ await s.claims.releaseLease("sess-1", "reused-owner");
322
+ assert.equal(await s.claims.drainedTurnsForOwner("sess-1", "reused-owner"), null);
323
+ assert.equal(JSON.parse(readFileSync(s.claims.leasePathForTest("sess-1"), "utf8")).pid, replacement.pid);
324
+ assert.deepEqual((await s.claims.load("sess-1"))?.entry.turns, [turn("pending")]);
325
+ } finally {
326
+ s.cleanup();
327
+ }
328
+ });
329
+
330
+ test("releaseLease preserves a replacement lease acquired under the claim lock", async () => {
331
+ const stateDir = mkdtempSync(path.join(os.tmpdir(), "remnic-drain-release-race-"));
332
+ const claims = new BufferDrainClaims(stateDir, plainIo());
333
+ const key = "sess-1";
334
+ const sessionPath = path.join(stateDir, "buffer", bufferSessionFileName(key));
335
+ const claimPath = path.join(stateDir, "buffer", "drain", bufferSessionFileName(key));
336
+ let release: Promise<void> | undefined;
337
+ try {
338
+ mkdirSync(path.dirname(sessionPath), { recursive: true });
339
+ writeFileSync(sessionPath, JSON.stringify({ key, ...entry([turn("a")]) }));
340
+ assert.equal(await claims.claimFromSessionFile(key, makeLease("owner-one")), "claimed");
341
+
342
+ await withHeldFileLock(`${claimPath}.lock`, { staleMs: 5_000 }, async (acquired) => {
343
+ assert.equal(acquired, true);
344
+ release = claims.releaseLease(key, "owner-one");
345
+ writeFileSync(claims.leasePathForTest(key), JSON.stringify(makeLease("owner-two")));
346
+ });
347
+ await release;
348
+
349
+ assert.equal(JSON.parse(readFileSync(claims.leasePathForTest(key), "utf8")).ownerId, "owner-two");
350
+ } finally {
351
+ rmSync(stateDir, { recursive: true, force: true });
352
+ }
353
+ });
354
+
355
+ test("ack preserves a replacement lease acquired under the claim lock", async () => {
356
+ const stateDir = mkdtempSync(path.join(os.tmpdir(), "remnic-drain-ack-race-"));
357
+ const claims = new BufferDrainClaims(stateDir, plainIo());
358
+ const key = "sess-1";
359
+ const sessionPath = path.join(stateDir, "buffer", bufferSessionFileName(key));
360
+ const claimPath = path.join(stateDir, "buffer", "drain", bufferSessionFileName(key));
361
+ let ack: Promise<AckOutcome> | undefined;
362
+ try {
363
+ mkdirSync(path.dirname(sessionPath), { recursive: true });
364
+ writeFileSync(sessionPath, JSON.stringify({ key, ...entry([turn("a")]) }));
365
+ assert.equal(await claims.claimFromSessionFile(key, makeLease("owner-one")), "claimed");
366
+ const claimed = await claims.load(key);
367
+ assert.ok(claimed);
368
+ await claims.writeEntry(key, "owner-one", entry([]), claimed.entry);
369
+
370
+ await withHeldFileLock(`${claimPath}.lock`, { staleMs: 5_000 }, async (acquired) => {
371
+ assert.equal(acquired, true);
372
+ ack = claims.ack(key, "owner-one");
373
+ writeFileSync(claims.leasePathForTest(key), JSON.stringify(makeLease("owner-two")));
374
+ });
375
+ await ack;
376
+
377
+ assert.equal(JSON.parse(readFileSync(claims.leasePathForTest(key), "utf8")).ownerId, "owner-two");
378
+ } finally {
379
+ rmSync(stateDir, { recursive: true, force: true });
380
+ }
381
+ });
382
+
187
383
  test("releaseLease keeps the claim content so a later run resumes it", async () => {
188
384
  const s = setup();
189
385
  try {
@@ -396,7 +592,7 @@ test("writeEntry rejects progress from a drainer that lost the lease", async ()
396
592
  /no longer owned/,
397
593
  );
398
594
  await s.claims.writeEntry("sess-1", "worker-two", entry([]), loaded.entry);
399
- assert.deepEqual((await s.claims.drainedTurns("sess-1")).map((t) => t.content), ["t1"], "the new owner's progress lands");
595
+ assert.deepEqual((await s.claims.drainedTurns("sess-1")).turns.map((t) => t.content), ["t1"], "the new owner's progress lands");
400
596
  } finally {
401
597
  s.cleanup();
402
598
  }
@@ -415,6 +611,7 @@ test("a retained turn the drainer extracted is tombstoned, not dropped", async (
415
611
  const claim = await s.claims.load("sess-1");
416
612
  assert.ok(claim);
417
613
  assert.deepEqual((claim.drainedRetained ?? []).map((t) => t.content), ["r1"], "the extracted retained copy is recorded");
614
+ assert.deepEqual((await s.claims.drainedTurns("sess-1")).retainedTurns.map((t) => t.content), ["r1"]);
418
615
  // A live owner still holding a stale retained copy cannot resurrect it.
419
616
  await s.claims.mergeIntoClaim("sess-1", { turns: [], retainedTurns: [turn("r1")], lastExtractionAt: null, extractionCount: 0 });
420
617
  const after = await s.claims.load("sess-1");
@@ -446,3 +643,130 @@ test("a turn the drainer retained for retry is not recorded as drained", async (
446
643
  s.cleanup();
447
644
  }
448
645
  });
646
+
647
+ test("a turn moved to retained and extracted from there cannot return from a stale active copy", async () => {
648
+ const s = setup();
649
+ try {
650
+ const moved = turn("moved to retained, then extracted");
651
+ s.seedSession("sess-1", entry([moved]));
652
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
653
+ const loaded = await s.claims.load("sess-1");
654
+ assert.ok(loaded);
655
+ await s.claims.writeEntry("sess-1", "worker-1", { ...entry([]), retainedTurns: [moved] }, loaded.entry);
656
+ const reloaded = await s.claims.load("sess-1");
657
+ assert.ok(reloaded);
658
+ await s.claims.writeEntry("sess-1", "worker-1", entry([]), reloaded.entry);
659
+
660
+ // A live owner still holds the turn in its active list and hands it over.
661
+ await s.claims.mergeIntoClaim("sess-1", entry([moved]));
662
+
663
+ const claim = await s.claims.load("sess-1");
664
+ assert.deepEqual(claim?.entry.turns, [], "the extracted turn is not pending again");
665
+ assert.deepEqual(claim?.entry.retainedTurns ?? [], []);
666
+ } finally {
667
+ s.cleanup();
668
+ }
669
+ });
670
+
671
+ test("a turn moved to active and extracted from there cannot return from a stale retained copy", async () => {
672
+ const s = setup();
673
+ try {
674
+ const moved = turn("moved to active, then extracted");
675
+ s.seedSession("sess-1", { ...entry([]), retainedTurns: [moved] });
676
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
677
+ const loaded = await s.claims.load("sess-1");
678
+ assert.ok(loaded);
679
+ await s.claims.writeEntry("sess-1", "worker-1", entry([moved]), loaded.entry);
680
+ const reloaded = await s.claims.load("sess-1");
681
+ assert.ok(reloaded);
682
+ await s.claims.writeEntry("sess-1", "worker-1", entry([]), reloaded.entry);
683
+
684
+ await s.claims.mergeIntoClaim("sess-1", { ...entry([]), retainedTurns: [moved] });
685
+
686
+ const claim = await s.claims.load("sess-1");
687
+ assert.deepEqual(claim?.entry.turns, []);
688
+ assert.deepEqual(claim?.entry.retainedTurns ?? [], [], "the extracted turn is not pending again");
689
+ } finally {
690
+ s.cleanup();
691
+ }
692
+ });
693
+
694
+ test("a turn that moves to retained and back is still tombstoned in its original list", async () => {
695
+ const s = setup();
696
+ try {
697
+ const roundTrip = turn("moves out and back");
698
+ s.seedSession("sess-1", entry([roundTrip]));
699
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
700
+ const loaded = await s.claims.load("sess-1");
701
+ assert.ok(loaded);
702
+ await s.claims.writeEntry("sess-1", "worker-1", { ...entry([]), retainedTurns: [roundTrip] }, loaded.entry);
703
+ const retained = await s.claims.load("sess-1");
704
+ assert.ok(retained);
705
+ await s.claims.writeEntry("sess-1", "worker-1", entry([roundTrip]), retained.entry);
706
+ const back = await s.claims.load("sess-1");
707
+ assert.ok(back);
708
+ await s.claims.writeEntry("sess-1", "worker-1", entry([]), back.entry);
709
+
710
+ assert.deepEqual(await s.claims.drainedTurns("sess-1"), { turns: [roundTrip], retainedTurns: [] });
711
+ } finally {
712
+ s.cleanup();
713
+ }
714
+ });
715
+
716
+ test("mergeIntoClaim keeps the claim's own pending occurrence after a partial extraction", async () => {
717
+ const s = setup();
718
+ try {
719
+ const same = turn("duplicate pending turn");
720
+ s.seedSession("sess-1", entry([same, same]));
721
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
722
+ const loaded = await s.claims.load("sess-1");
723
+ assert.ok(loaded);
724
+ await s.claims.writeEntry("sess-1", "worker-1", entry([same]), loaded.entry);
725
+
726
+ // The live owner hands over a copy that adds nothing new.
727
+ await s.claims.mergeIntoClaim("sess-1", entry([]));
728
+
729
+ assert.deepEqual((await s.claims.load("sess-1"))?.entry.turns, [same], "the remainder is not subtracted a second time");
730
+ } finally {
731
+ s.cleanup();
732
+ }
733
+ });
734
+
735
+ test("repeated progress writes record each extracted occurrence once", async () => {
736
+ const s = setup();
737
+ try {
738
+ const same = turn("duplicate pending turn");
739
+ s.seedSession("sess-1", entry([same, same]));
740
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
741
+ const loaded = await s.claims.load("sess-1");
742
+ assert.ok(loaded);
743
+ // The drain runner's buffer keeps ONE snapshot of the claim for the whole run.
744
+ const storage = new ClaimBufferStorage(s.claims, "sess-1", "worker-1", loaded.entry);
745
+ await storage.saveBufferSession("sess-1", entry([same]));
746
+ await storage.saveBufferSession("sess-1", entry([]));
747
+
748
+ assert.deepEqual(await s.claims.drainedTurns("sess-1"), { turns: [same, same], retainedTurns: [] });
749
+ } finally {
750
+ s.cleanup();
751
+ }
752
+ });
753
+
754
+ test("a late arrival of an ambiguous identity does not replace a source occurrence", async () => {
755
+ const s = setup();
756
+ try {
757
+ const same = turn("same identity in both lists");
758
+ s.seedSession("sess-1", { ...entry([same]), retainedTurns: [same] });
759
+ await s.claims.claimFromSessionFile("sess-1", makeLease());
760
+ const loaded = await s.claims.load("sess-1");
761
+ assert.ok(loaded);
762
+ await s.claims.mergeIntoClaim("sess-1", entry([same, same]));
763
+
764
+ await s.claims.writeEntry("sess-1", "worker-1", { ...entry([]), retainedTurns: [same] }, loaded.entry);
765
+
766
+ const claim = await s.claims.load("sess-1");
767
+ assert.deepEqual(claim?.entry.turns, [same, same], "the source occurrence and the late arrival both stay pending");
768
+ assert.deepEqual(claim?.entry.retainedTurns, [same]);
769
+ } finally {
770
+ s.cleanup();
771
+ }
772
+ });