@remnic/core 10.0.84 → 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 (399) 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 +146 -144
  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.js +78 -77
  25. package/dist/access-recall-concurrency.js +67 -66
  26. package/dist/access-recall-response.js +67 -66
  27. package/dist/access-recall-surface.js +67 -66
  28. package/dist/access-schema.js +4 -4
  29. package/dist/access-service.js +67 -66
  30. package/dist/access-surface-catalog.js +69 -68
  31. package/dist/access-surface-catalog.js.map +1 -1
  32. package/dist/active-recall.js +31 -31
  33. package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
  34. package/dist/briefing.js +25 -25
  35. package/dist/buffer-persistence.d.ts +8 -4
  36. package/dist/buffer-persistence.d.ts.map +1 -1
  37. package/dist/buffer-persistence.js +1 -1
  38. package/dist/buffer-turn-helpers.d.ts +5 -0
  39. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  40. package/dist/buffer-turn-helpers.js +1 -1
  41. package/dist/buffer.d.ts +4 -1
  42. package/dist/buffer.d.ts.map +1 -1
  43. package/dist/buffer.js +9 -9
  44. package/dist/calibration.js +2 -2
  45. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
  46. package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
  47. package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
  48. package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
  49. package/dist/causal-behavior.js +2 -2
  50. package/dist/causal-chain.js +2 -2
  51. package/dist/causal-consolidation.js +32 -32
  52. package/dist/causal-retrieval.js +2 -2
  53. package/dist/causal-trajectory-graph.js +2 -2
  54. package/dist/causal-trajectory.js +1 -1
  55. package/dist/{chunk-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
  56. package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
  57. package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
  58. package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
  59. package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
  60. package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
  61. package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
  62. package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
  63. package/dist/chunk-3ODZLCHC.js.map +1 -0
  64. package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
  65. package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
  66. package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
  67. package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
  68. package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
  69. package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
  70. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  71. package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
  72. package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
  73. package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
  74. package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
  75. package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
  76. package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
  77. package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
  78. package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
  79. package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
  80. package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
  81. package/dist/chunk-BMIM6ESC.js.map +1 -0
  82. package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
  83. package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
  84. package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
  85. package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
  86. package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
  87. package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
  88. package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
  89. package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
  90. package/dist/{chunk-K3B72DKJ.js → chunk-E7V5AK7I.js} +2 -2
  91. package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
  92. package/dist/chunk-ELWI2R6H.js.map +1 -0
  93. package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
  94. package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
  95. package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
  96. package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
  97. package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
  98. package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
  99. package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
  100. package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
  101. package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
  102. package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
  103. package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
  104. package/dist/chunk-H34INI3P.js.map +1 -0
  105. package/dist/{chunk-HLKUZW42.js → chunk-H4D5YURJ.js} +80 -94
  106. package/dist/chunk-H4D5YURJ.js.map +1 -0
  107. package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
  108. package/dist/chunk-HVFX2N2G.js.map +1 -0
  109. package/dist/chunk-IGLJNHOX.js +62 -0
  110. package/dist/chunk-IGLJNHOX.js.map +1 -0
  111. package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
  112. package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
  113. package/dist/chunk-JFH6JAGY.js.map +1 -0
  114. package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
  115. package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
  116. package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
  117. package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
  118. package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
  119. package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
  120. package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
  121. package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
  122. package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
  123. package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
  124. package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
  125. package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
  126. package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
  127. package/dist/{chunk-3JPTUCD7.js → chunk-ONN2E5QD.js} +32 -23
  128. package/dist/chunk-ONN2E5QD.js.map +1 -0
  129. package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
  130. package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
  131. package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
  132. package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
  133. package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
  134. package/dist/chunk-QV2VZKD5.js +31 -0
  135. package/dist/chunk-QV2VZKD5.js.map +1 -0
  136. package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
  137. package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
  138. package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
  139. package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
  140. package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
  141. package/dist/chunk-SGHV6LZ7.js.map +1 -0
  142. package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
  143. package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
  144. package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
  145. package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
  146. package/dist/chunk-UDKWFDV2.js.map +1 -0
  147. package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
  148. package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
  149. package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
  150. package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
  151. package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
  152. package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
  153. package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
  154. package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
  155. package/dist/chunk-WLNWFHX3.js.map +1 -0
  156. package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
  157. package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
  158. package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
  159. package/dist/chunk-XO23OKMK.js.map +1 -0
  160. package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
  161. package/dist/chunk-XSFQN7TP.js.map +1 -0
  162. package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
  163. package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
  164. package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
  165. package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
  166. package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
  167. package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
  168. package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
  169. package/dist/cli.js +110 -108
  170. package/dist/coding/export-okf-codegraph.js +25 -25
  171. package/dist/coding/pre-action-gate.js +2 -2
  172. package/dist/compounding/engine.js +27 -27
  173. package/dist/config.js +28 -28
  174. package/dist/connectors/codex-materialize-runner.js +25 -25
  175. package/dist/connectors/index.js +25 -25
  176. package/dist/contradiction/index.js +4 -4
  177. package/dist/convergence-refresh.js +27 -27
  178. package/dist/conversation-index/backend.js +2 -2
  179. package/dist/corpus-watermark.js +27 -27
  180. package/dist/deep-recall-http-glue.js +5 -5
  181. package/dist/entity-retrieval.js +25 -25
  182. package/dist/event-order-recall.js +2 -2
  183. package/dist/explicit-capture.js +5 -5
  184. package/dist/external-wiki-access.js +68 -67
  185. package/dist/extraction-evidence-class.js +2 -2
  186. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  187. package/dist/extraction-quote-grounding.js +1 -1
  188. package/dist/extraction-source-grounding-rules.js +2 -2
  189. package/dist/extraction-source-grounding.js +5 -5
  190. package/dist/extraction-span-materialize.js +2 -2
  191. package/dist/extraction.js +40 -40
  192. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  193. package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
  194. package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
  195. package/dist/graph-jsonl.js +3 -3
  196. package/dist/graph-snapshot.js +3 -3
  197. package/dist/graph.js +2 -2
  198. package/dist/harmonic-retrieval.js +25 -25
  199. package/dist/index.js +195 -193
  200. package/dist/lcm/engine.js +2 -2
  201. package/dist/lcm/index.js +2 -2
  202. package/dist/local-llm.js +2 -2
  203. package/dist/location/index.js +28 -28
  204. package/dist/maintenance/memory-governance.js +25 -25
  205. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
  206. package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
  207. package/dist/maintenance/rebuild-memory-projection.js +26 -26
  208. package/dist/memory-browse-operations.js +68 -67
  209. package/dist/memory-subject.js +2 -2
  210. package/dist/namespaces/migrate.js +26 -26
  211. package/dist/namespaces/search.d.ts +8 -0
  212. package/dist/namespaces/search.d.ts.map +1 -1
  213. package/dist/namespaces/search.js +10 -6
  214. package/dist/namespaces/storage.js +25 -25
  215. package/dist/operator-doctor-corpus.js +28 -28
  216. package/dist/operator-toolkit.js +39 -39
  217. package/dist/orchestration/backlog-scheduler.d.ts +3 -2
  218. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  219. package/dist/orchestration/backlog-scheduler.js +157 -155
  220. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  221. package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
  222. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  223. package/dist/orchestration/buffer-drain-runner.js +147 -145
  224. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  225. package/dist/orchestration/fact-dedup-window.d.ts +1 -4
  226. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  227. package/dist/orchestration/maintenance.js +39 -39
  228. package/dist/orchestrator.js +146 -144
  229. package/dist/provenance.js +2 -2
  230. package/dist/recall-navigation-operations.js +68 -67
  231. package/dist/recall-navigation.js +67 -66
  232. package/dist/reconcile/manifest.js +2 -2
  233. package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
  234. package/dist/resolution-23ZBDM6A.js.map +1 -0
  235. package/dist/resume-bundles.js +29 -29
  236. package/dist/salvage-envelope.js +4 -4
  237. package/dist/search/factory.js +5 -5
  238. package/dist/search/index.js +9 -9
  239. package/dist/semantic-consolidation.js +26 -26
  240. package/dist/semantic-rule-promotion.js +25 -25
  241. package/dist/semantic-rule-verifier.js +25 -25
  242. package/dist/source-agent-qualifier.js +31 -31
  243. package/dist/standup/build.js +25 -25
  244. package/dist/storage/buffer-drain-claims.d.ts +18 -6
  245. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  246. package/dist/storage/buffer-drain-claims.js +2 -2
  247. package/dist/storage/buffer-session-files.js +2 -2
  248. package/dist/storage/content-hash-index.d.ts +8 -0
  249. package/dist/storage/content-hash-index.d.ts.map +1 -1
  250. package/dist/storage.d.ts +10 -1
  251. package/dist/storage.d.ts.map +1 -1
  252. package/dist/storage.js +24 -24
  253. package/dist/subject-config.js +3 -3
  254. package/dist/summarizer.js +3 -3
  255. package/dist/support-passport/index.js +70 -69
  256. package/dist/transfer/autodetect.js +1 -1
  257. package/dist/transfer/backup.js +2 -2
  258. package/dist/transfer/capsule-export.js +3 -3
  259. package/dist/transfer/capsule-gunzip.d.ts +14 -0
  260. package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
  261. package/dist/transfer/capsule-import.d.ts.map +1 -1
  262. package/dist/transfer/capsule-import.js +3 -2
  263. package/dist/transfer/capsule-merge.d.ts.map +1 -1
  264. package/dist/transfer/export-okf.js +25 -25
  265. package/dist/verified-recall.js +25 -25
  266. package/dist/who-knows-cli.js +26 -26
  267. package/dist/who-knows.js +25 -25
  268. package/dist/write-envelope.js +3 -3
  269. package/package.json +2 -2
  270. package/src/access-mcp-frames.test.ts +47 -0
  271. package/src/access-mcp-frames.ts +74 -0
  272. package/src/access-mcp.test.ts +146 -0
  273. package/src/access-mcp.ts +30 -30
  274. package/src/access-observe-write-surface.ts +4 -4
  275. package/src/backlog-scheduler.test.ts +443 -3
  276. package/src/buffer-persistence.ts +15 -6
  277. package/src/buffer-turn-helpers.ts +6 -0
  278. package/src/buffer.ts +6 -6
  279. package/src/cli.ts +4 -4
  280. package/src/extraction-quote-grounding.test.ts +49 -0
  281. package/src/extraction-quote-grounding.ts +50 -12
  282. package/src/fact-dedup-window.test.ts +103 -20
  283. package/src/namespaces/search.test.ts +339 -0
  284. package/src/namespaces/search.ts +54 -1
  285. package/src/orchestration/backlog-scheduler.ts +13 -12
  286. package/src/orchestration/buffer-drain-runner.ts +40 -16
  287. package/src/orchestration/extraction-persist.ts +2 -10
  288. package/src/orchestration/fact-dedup-window.ts +5 -12
  289. package/src/storage/buffer-drain-claims.test.ts +330 -6
  290. package/src/storage/buffer-drain-claims.ts +226 -80
  291. package/src/storage/buffer-session-files.ts +2 -2
  292. package/src/storage/content-hash-index.ts +9 -0
  293. package/src/storage.ts +15 -0
  294. package/src/transfer/capsule-gunzip.ts +39 -0
  295. package/src/transfer/capsule-import.test.ts +73 -0
  296. package/src/transfer/capsule-import.ts +2 -2
  297. package/src/transfer/capsule-merge.test.ts +34 -0
  298. package/src/transfer/capsule-merge.ts +2 -10
  299. package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
  300. package/dist/chunk-3JPTUCD7.js.map +0 -1
  301. package/dist/chunk-4KLQI3L2.js.map +0 -1
  302. package/dist/chunk-6LCWKS62.js.map +0 -1
  303. package/dist/chunk-7PMOCQOL.js.map +0 -1
  304. package/dist/chunk-B74NPUHT.js.map +0 -1
  305. package/dist/chunk-HLKUZW42.js.map +0 -1
  306. package/dist/chunk-KQUSP6LB.js.map +0 -1
  307. package/dist/chunk-LU66I56Q.js.map +0 -1
  308. package/dist/chunk-MPDLLLOG.js.map +0 -1
  309. package/dist/chunk-WAD32CZG.js.map +0 -1
  310. package/dist/chunk-XYP5WBDO.js.map +0 -1
  311. package/dist/chunk-YK7GOPIE.js.map +0 -1
  312. package/dist/chunk-Z3DDQXM4.js.map +0 -1
  313. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
  314. /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
  315. /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  316. /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
  317. /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
  318. /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
  319. /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
  320. /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
  321. /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
  322. /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
  323. /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
  324. /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
  325. /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
  326. /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
  327. /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
  328. /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
  329. /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
  330. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  331. /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
  332. /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
  333. /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
  334. /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
  335. /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
  336. /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
  337. /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
  338. /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
  339. /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
  340. /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
  341. /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
  342. /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
  343. /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
  344. /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
  345. /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
  346. /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
  347. /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
  348. /package/dist/{chunk-K3B72DKJ.js.map → chunk-E7V5AK7I.js.map} +0 -0
  349. /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
  350. /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
  351. /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
  352. /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
  353. /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
  354. /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
  355. /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
  356. /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
  357. /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
  358. /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
  359. /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
  360. /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
  361. /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
  362. /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
  363. /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
  364. /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
  365. /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
  366. /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
  367. /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
  368. /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
  369. /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
  370. /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
  371. /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
  372. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
  373. /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
  374. /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
  375. /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
  376. /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
  377. /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
  378. /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
  379. /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
  380. /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
  381. /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
  382. /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
  383. /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
  384. /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
  385. /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
  386. /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
  387. /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
  388. /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
  389. /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
  390. /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
  391. /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
  392. /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
  393. /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
  394. /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
  395. /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
  396. /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
  397. /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
  398. /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
  399. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
package/src/access-mcp.ts CHANGED
@@ -30,7 +30,9 @@ import {
30
30
  validateRequest,
31
31
  } from "./access-schema.js";
32
32
  import { EngramAccessInputError, type EngramAccessRecallResponse, type EngramAccessService } from "./access-service.js";
33
- import { unknownToolError } from "./access-errors.js";
33
+ import { EngramAccessForbiddenError, unknownToolError } from "./access-errors.js";
34
+ import { log } from "./logger.js";
35
+ import { McpFrameReader } from "./access-mcp-frames.js";
34
36
  import "./access-operations.js";
35
37
  import { validateBriefingFormat } from "./briefing.js";
36
38
  import { processChatMessage } from "./chat/chat-factory.js";
@@ -88,6 +90,19 @@ function throwMcpAbort(signal: AbortSignal | undefined, message: string): void {
88
90
  throw abortError(message);
89
91
  }
90
92
 
93
+ /**
94
+ * Map tools/call failures to a client-safe message. Unexpected backend/fs
95
+ * errors become a stable generic string; detail stays in the server log.
96
+ * Intended EngramAccessInputError / EngramAccessForbiddenError stay readable.
97
+ */
98
+ function mcpToolErrorMessage(err: Error): string {
99
+ if (err instanceof EngramAccessInputError || err instanceof EngramAccessForbiddenError) {
100
+ return err.message;
101
+ }
102
+ log.error("engram MCP tools/call internal error", err);
103
+ return "internal error";
104
+ }
105
+
91
106
  type McpRequestOptions = {
92
107
  principalOverride?: string;
93
108
  namespaceOverride?: string;
@@ -406,7 +421,7 @@ function parseMcpRequest<N extends SchemaName>(schemaName: N, args: Record<strin
406
421
  function optionalNonEmptyString(value: unknown, label: string): string | undefined {
407
422
  if (value === undefined || value === null || value === "") return undefined;
408
423
  if (typeof value !== "string") {
409
- throw new Error(`${label} expects a string; got ${JSON.stringify(value)}`);
424
+ throw new EngramAccessInputError(`${label} expects a string; got ${JSON.stringify(value)}`);
410
425
  }
411
426
  return value;
412
427
  }
@@ -420,11 +435,11 @@ function optionalNonEmptyString(value: unknown, label: string): string | undefin
420
435
  function optionalPositiveInteger(value: unknown, label: string): number | undefined {
421
436
  if (value === undefined || value === null || value === "") return undefined;
422
437
  if (typeof value !== "number" && typeof value !== "string") {
423
- throw new Error(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
438
+ throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
424
439
  }
425
440
  const parsed = typeof value === "number" ? value : Number(value);
426
441
  if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
427
- throw new Error(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
442
+ throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
428
443
  }
429
444
  return parsed;
430
445
  }
@@ -447,7 +462,7 @@ async function getMcpServerVersion(): Promise<string> {
447
462
  }
448
463
 
449
464
  export class EngramMcpServer {
450
- private buffer = Buffer.alloc(0);
465
+ private readonly frames = new McpFrameReader();
451
466
  private flushTask: Promise<void> | null = null;
452
467
  private readonly tools: McpTool[];
453
468
  private readonly resources: McpResource[];
@@ -2402,7 +2417,7 @@ export class EngramMcpServer {
2402
2417
  // Cancellation is transport control flow, not a JSON-RPC tool error.
2403
2418
  // Preserve the original AbortError so HTTP can silently end a dead socket.
2404
2419
  if (isAbortError(err)) throw err;
2405
- const message = err instanceof Error ? err.message : String(err);
2420
+ const message = mcpToolErrorMessage(err instanceof Error ? err : new Error(String(err)));
2406
2421
  return {
2407
2422
  jsonrpc: "2.0",
2408
2423
  id,
@@ -2426,7 +2441,7 @@ export class EngramMcpServer {
2426
2441
 
2427
2442
  async runStdio(input: Readable, output: Writable): Promise<void> {
2428
2443
  input.on("data", (chunk) => {
2429
- this.buffer = Buffer.concat([this.buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
2444
+ this.frames.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2430
2445
  this.scheduleFlush(output);
2431
2446
  });
2432
2447
  await new Promise<void>((resolve, reject) => {
@@ -2455,7 +2470,7 @@ export class EngramMcpServer {
2455
2470
  if (this.flushTask === task) {
2456
2471
  this.flushTask = null;
2457
2472
  }
2458
- if (this.buffer.length > 0) {
2473
+ if (this.frames.hasUnread()) {
2459
2474
  this.scheduleFlush(output);
2460
2475
  }
2461
2476
  });
@@ -2463,30 +2478,15 @@ export class EngramMcpServer {
2463
2478
  }
2464
2479
 
2465
2480
  private async flushBuffer(output: Writable): Promise<void> {
2466
- while (true) {
2467
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
2468
- if (headerEnd < 0) return;
2469
- const headerText = this.buffer.slice(0, headerEnd).toString("utf-8");
2470
- const headers = headerText.split("\r\n");
2471
- const contentLengthHeader = headers.find((line) => line.toLowerCase().startsWith("content-length:"));
2472
- if (!contentLengthHeader) {
2473
- this.buffer = Buffer.alloc(0);
2474
- return;
2475
- }
2476
- const contentLength = Number.parseInt(contentLengthHeader.split(":")[1]?.trim() ?? "0", 10);
2477
- if (!Number.isFinite(contentLength) || contentLength < 0) {
2478
- this.buffer = Buffer.alloc(0);
2479
- return;
2481
+ for (let frame = this.frames.next(); frame; frame = this.frames.next()) {
2482
+ if (frame.kind === "too-large") {
2483
+ this.writeMessage(output, { jsonrpc: "2.0", id: null, error: { code: -32600, message: "frame too large" } });
2484
+ continue;
2480
2485
  }
2481
- const messageStart = headerEnd + 4;
2482
- const messageEnd = messageStart + contentLength;
2483
- if (this.buffer.length < messageEnd) return;
2484
- const body = this.buffer.slice(messageStart, messageEnd).toString("utf-8");
2485
- this.buffer = this.buffer.slice(messageEnd);
2486
-
2487
2486
  let parsed: JsonRpcRequest;
2488
2487
  try {
2489
- parsed = JSON.parse(body) as JsonRpcRequest;
2488
+ // SAFETY: handleRequest defaults missing fields; a non-object body throws and scheduleFlush answers -32700.
2489
+ parsed = JSON.parse(frame.text) as JsonRpcRequest;
2490
2490
  } catch {
2491
2491
  this.writeMessage(output, {
2492
2492
  jsonrpc: "2.0",
@@ -2620,7 +2620,7 @@ export class EngramMcpServer {
2620
2620
  caps?.ops === undefined || capabilityAllowsOp(caps, MCP_MIGRATED_OPERATIONS[toLegacyToolName(t.name)] ?? "")
2621
2621
  )
2622
2622
  .map((t) => t.name);
2623
- throw new Error(unknownToolError(name, visible));
2623
+ throw new EngramAccessInputError(unknownToolError(name, visible));
2624
2624
  }
2625
2625
  const op = getOperation(migrated);
2626
2626
  if (!op) {
@@ -51,6 +51,7 @@ import {
51
51
  queueExplicitCaptureForReview,
52
52
  } from "./explicit-capture.js";
53
53
  import { log } from "./logger.js";
54
+ import { embeddingBacklogExceeds, isSearchHealthDegraded } from "./namespaces/search.js";
54
55
  import { recordObjectiveStateSnapshotsFromObservedMessages } from "./objective-state-writers.js";
55
56
  import type { Orchestrator } from "./orchestrator.js";
56
57
  import { displayErrorDetail } from "./runtime/better-sqlite.js";
@@ -289,7 +290,7 @@ export class AccessObserveWriteSurface {
289
290
  /* status probe failed — non-fatal */
290
291
  }
291
292
  }
292
- if (threshold > 0 && pendingEmbeddings !== null && pendingEmbeddings > threshold) {
293
+ if (embeddingBacklogExceeds(pendingEmbeddings, threshold)) {
293
294
  degraded = true;
294
295
  degradedReason = `embedding-backlog: ${pendingEmbeddings} pending > threshold ${threshold}`;
295
296
  }
@@ -355,14 +356,13 @@ export class AccessObserveWriteSurface {
355
356
  const pending = health.pendingEmbeddings ?? null;
356
357
  const oldest = health.oldestPendingAgeMs ?? null;
357
358
  const threshold = this.deps.orchestrator.config.qmdEmbeddingBacklogThreshold;
358
- const backlogDegraded = threshold > 0 && pending !== null && pending > threshold;
359
- const degraded = !active || health.collectionState === "unknown" || backlogDegraded;
359
+ const backlogDegraded = embeddingBacklogExceeds(pending, threshold);
360
360
  const mode = !active ? "fallback" : health.daemonMode === true ? "daemon" : "cli";
361
361
 
362
362
  return {
363
363
  enabled: true,
364
364
  active,
365
- degraded,
365
+ degraded: isSearchHealthDegraded({ ...health, pendingEmbeddings: pending }, threshold),
366
366
  mode,
367
367
  collection: health.collection || fallbackCollection,
368
368
  collectionState: health.collectionState,
@@ -11,6 +11,7 @@
11
11
  import assert from "node:assert/strict";
12
12
  import test from "node:test";
13
13
 
14
+ import { hostname } from "node:os";
14
15
  import {
15
16
  BacklogScheduler,
16
17
  type BacklogSchedulerDeps,
@@ -23,10 +24,12 @@ import { Orchestrator } from "./orchestrator.js";
23
24
  import {
24
25
  cleanupDir,
25
26
  makeHarmonicLifecycleConfig,
27
+ memoryFilesContaining,
26
28
  mkTempMemoryDir,
27
29
  singleFactResult,
28
30
  stubExtraction,
29
31
  } from "./testing/orchestrator-lite.js";
32
+ import type { DrainLease } from "./storage/buffer-drain-claims.js";
30
33
  import type { BufferTurn } from "./types.js";
31
34
 
32
35
  interface StubState {
@@ -79,10 +82,10 @@ function makeDeps(state: StubState, queue: ExtractionQueueCoordinator): BacklogS
79
82
  },
80
83
  },
81
84
  liveBufferCounts: () => state.counts,
82
- lease: () => ({
83
- ownerId: "daemon-drain-test",
85
+ lease: (ownerId) => ({
86
+ ownerId,
84
87
  pid: process.pid,
85
- host: "test",
88
+ host: hostname(),
86
89
  acquiredAt: new Date().toISOString(),
87
90
  heartbeatAt: new Date().toISOString(),
88
91
  leaseMs: 60_000,
@@ -113,6 +116,50 @@ function turn(content: string, sessionKey: string, timestamp = "2026-11-01T00:00
113
116
  return { role: "user", content, timestamp, sessionKey };
114
117
  }
115
118
 
119
+ test("scheduler instances use distinct lease tokens through claim and drain", async () => {
120
+ const issuedOwners: string[] = [];
121
+ const drainedOwners: string[] = [];
122
+ for (let attempt = 0; attempt < 2; attempt += 1) {
123
+ const key = `token-session-${attempt}`;
124
+ const queue = new ExtractionQueueCoordinator(1);
125
+ const state: StubState = {
126
+ counts: new Map([[key, { turns: 1, retainedTurns: 0 }]]),
127
+ claimed: new Set(),
128
+ claimTurns: new Map(),
129
+ claimedThisRun: new Set(),
130
+ outcomes: [emptyOutcome(key)],
131
+ drained: [],
132
+ };
133
+ const deps = makeDeps(state, queue);
134
+ deps.lease = (ownerId) => {
135
+ issuedOwners.push(ownerId);
136
+ return {
137
+ ownerId,
138
+ pid: process.pid,
139
+ host: hostname(),
140
+ acquiredAt: new Date().toISOString(),
141
+ heartbeatAt: new Date().toISOString(),
142
+ leaseMs: 60_000,
143
+ };
144
+ };
145
+ deps.drainKey = async (_drainKey, drainOptions) => {
146
+ drainedOwners.push(drainOptions.ownerId);
147
+ return emptyOutcome(key);
148
+ };
149
+ const scheduler = new BacklogScheduler(deps, { ownerId: "shared-label", pollMs: 3_600_000 });
150
+ scheduler.start();
151
+ try {
152
+ await scheduler.tickForTest();
153
+ await scheduler.settleForTest();
154
+ } finally {
155
+ await scheduler.stop();
156
+ }
157
+ }
158
+
159
+ assert.notEqual(issuedOwners[0], issuedOwners[1]);
160
+ assert.deepEqual([...new Set(drainedOwners)], [...new Set(issuedOwners)]);
161
+ });
162
+
116
163
  test("startup drains existing claims and newly buffered sessions within the queue's concurrency", async () => {
117
164
  const queue = new ExtractionQueueCoordinator(2);
118
165
  const state: StubState = {
@@ -464,6 +511,399 @@ test("a wired scheduler drains real backlog and resumes after tombstone-consumin
464
511
  }
465
512
  });
466
513
 
514
+ // ── Real wiring: full cap cycle (fill → refuse → drain → admit) ─────────────
515
+
516
+ test("a full session cap admits a new durable session after the backlog drains", async () => {
517
+ // Real production SmartBuffer admission, real backlog scheduler/drain and
518
+ // the real persistence path (session files → drain claims → memory files).
519
+ // The extraction MODEL is a test double: it records the exact turn slice it
520
+ // is asked to extract and derives one fact from the first turn's content —
521
+ // every durability, admission and clearing decision below is production code.
522
+ const memoryDir = await mkTempMemoryDir("backlog-cap-cycle");
523
+ let orchestrator: Orchestrator | undefined;
524
+ try {
525
+ orchestrator = new Orchestrator(
526
+ makeHarmonicLifecycleConfig(memoryDir, {
527
+ extractionMinChars: 0,
528
+ extractionMinUserTurns: 1,
529
+ bufferMaxSessions: 4, // + the legacy `default` sentinel entry: 3 durable slots
530
+ })
531
+ );
532
+ const calls = stubExtraction(orchestrator, (turns) => singleFactResult(`cap-cycle fact for ${turns[0]?.content ?? "empty"}`));
533
+ const buffer = orchestrator.buffer;
534
+ const defaultNamespace = orchestrator.config.defaultNamespace;
535
+
536
+ // Mirrors production ingestion (turn-ingestion.ts): accepted captures are
537
+ // pinned BEFORE bounded admission, so the cap applies to real durable work.
538
+ const pinned = (key: string, content: string): BufferTurn => ({
539
+ ...turn(content, key),
540
+ acceptedCapture: true,
541
+ writeNamespaceOverride: defaultNamespace,
542
+ });
543
+ const capture = async (key: string, content: string): Promise<void> => {
544
+ const outcome = await buffer.addTurnBounded(key, pinned(key, content));
545
+ assert.notEqual(outcome, null, `${key} admitted below the cap`);
546
+ await buffer.flushPendingSave({ throwOnFailure: true });
547
+ };
548
+ await capture("cap-1", "alpha one");
549
+ await capture("cap-2", "beta two");
550
+ await capture("cap-3", "gamma three");
551
+
552
+ // The cap genuinely bites: a fourth durable session is refused, never
553
+ // evicted (#3251).
554
+ assert.equal(
555
+ await orchestrator.buffer.addTurnBounded("cap-4", turn("delta four", "cap-4")),
556
+ null,
557
+ "a fourth session is refused at the cap"
558
+ );
559
+
560
+ const scheduler = wireBacklogScheduler(orchestrator, { pollMs: 3_600_000 });
561
+ try {
562
+ scheduler.start();
563
+ for (let attempt = 0; attempt < 6 && scheduler.snapshot().counters.processed < 3; attempt += 1) {
564
+ await scheduler.tickForTest();
565
+ await scheduler.settleForTest();
566
+ }
567
+ assert.equal(scheduler.snapshot().counters.processed, 3, "all three sessions drained");
568
+ assert.equal(scheduler.snapshot().counters.durableOutputs, 3, "one durable output per session");
569
+ assert.equal(calls.length, 3, "the model double saw one turn slice per session");
570
+
571
+ // Durable extraction kept the content: each session's fact is on disk.
572
+ for (const source of ["alpha one", "beta two", "gamma three"]) {
573
+ const hits = await memoryFilesContaining(memoryDir, `cap-cycle fact for ${source}`);
574
+ assert.ok(hits.length > 0, `content of the drained session was lost: ${source}`);
575
+ }
576
+
577
+
578
+ // The source turns are cleared from both the durable claim and the live
579
+ // SmartBuffer; the claim tombstones remain until a later owner save.
580
+ const claims = orchestrator.storage.bufferDrainClaims();
581
+ assert.deepEqual(
582
+ (await claims.list()).map((l) => l.turnCount).sort(),
583
+ [0, 0, 0],
584
+ "all drained claims are empty tombstones"
585
+ );
586
+ for (const key of ["cap-1", "cap-2", "cap-3"]) {
587
+ assert.deepEqual(orchestrator.buffer.getTurns(key), [], `${key} stale source turns cleared in memory`);
588
+ }
589
+
590
+ const admitted = await orchestrator.buffer.addTurnBounded("cap-4", pinned("cap-4", "delta four"));
591
+ assert.notEqual(admitted, null, "a fully drained backlog frees cap slots for a new session");
592
+ } finally {
593
+ await scheduler.stop();
594
+ }
595
+ } finally {
596
+ await orchestrator?.destroy().catch(() => undefined);
597
+ await cleanupDir(memoryDir);
598
+ }
599
+ });
600
+
601
+
602
+ test("same-key accepted ingress during extraction survives owner-fenced reconciliation", async () => {
603
+ const memoryDir = await mkTempMemoryDir("backlog-drain-ingress-race");
604
+ let orchestrator: Orchestrator | undefined;
605
+ try {
606
+ orchestrator = new Orchestrator(
607
+ makeHarmonicLifecycleConfig(memoryDir, { extractionMinChars: 0, extractionMinUserTurns: 1 })
608
+ );
609
+ const buffer = orchestrator.buffer;
610
+ const namespace = orchestrator.config.defaultNamespace;
611
+ const key = "race-session";
612
+ const oldTurn: BufferTurn = {
613
+ ...turn("original captured turn", key),
614
+ acceptedCapture: true,
615
+ writeNamespaceOverride: namespace,
616
+ };
617
+ const newerTurn: BufferTurn = {
618
+ ...turn("new accepted turn during drain", key, "2026-11-02T00:00:00.000Z"),
619
+ acceptedCapture: true,
620
+ writeNamespaceOverride: namespace,
621
+ principalOverride: "principal-race",
622
+ };
623
+ const entered = Promise.withResolvers<void>();
624
+ const release = Promise.withResolvers<void>();
625
+ let firstExtraction = true;
626
+ const calls = stubExtraction(orchestrator, async (turns) => {
627
+ if (firstExtraction) {
628
+ firstExtraction = false;
629
+ entered.resolve();
630
+ await release.promise;
631
+ }
632
+ return singleFactResult(`race fact for ${turns[0]?.content ?? "empty"}`);
633
+ });
634
+
635
+ assert.notEqual(await buffer.addTurnBounded(key, oldTurn), null);
636
+ await buffer.flushPendingSave({ throwOnFailure: true });
637
+ let now = 0;
638
+ const scheduler = wireBacklogScheduler(orchestrator, {
639
+ ownerId: "race-owner",
640
+ pollMs: 3_600_000,
641
+ retryBaseMs: 1,
642
+ retryMaxMs: 1,
643
+ now: () => now,
644
+ });
645
+ try {
646
+ scheduler.start();
647
+ await entered.promise;
648
+ assert.equal(
649
+ await orchestrator.storage.bufferDrainClaims().drainedTurnsForOwner(key, "not-race-owner"),
650
+ null,
651
+ "another owner cannot authorize live-buffer reconciliation"
652
+ );
653
+
654
+ // Live ingress saves into the owned claim while the model is still
655
+ // extracting its original snapshot.
656
+ assert.notEqual(await buffer.addTurnBounded(key, newerTurn), null);
657
+ await buffer.flushPendingSave({ throwOnFailure: true });
658
+ release.resolve();
659
+ await scheduler.settleForTest();
660
+
661
+ const pending = await orchestrator.storage.bufferDrainClaims().load(key);
662
+ assert.deepEqual(
663
+ pending?.entry.turns.map((item) => item.content),
664
+ ["new accepted turn during drain"],
665
+ "the extracted source is removed while newer accepted ingress remains durable"
666
+ );
667
+ assert.equal(pending?.entry.turns[0]?.principalOverride, "principal-race");
668
+ assert.equal(pending?.entry.turns[0]?.writeNamespaceOverride, namespace);
669
+ assert.equal((await memoryFilesContaining(memoryDir, "race fact for original captured turn")).length, 1);
670
+
671
+ now = 2;
672
+ await scheduler.tickForTest();
673
+ await scheduler.settleForTest();
674
+ assert.equal(scheduler.snapshot().counters.processed, 1, "the newer turn also drains");
675
+ assert.deepEqual(calls.map((batch) => batch.map((item) => item.content)), [
676
+ ["original captured turn"],
677
+ ["new accepted turn during drain"],
678
+ ]);
679
+ assert.equal(calls[1]?.[0]?.principalOverride, "principal-race");
680
+ assert.equal(calls[1]?.[0]?.writeNamespaceOverride, namespace);
681
+ assert.equal((await memoryFilesContaining(memoryDir, "race fact for original captured turn")).length, 1);
682
+ assert.equal((await memoryFilesContaining(memoryDir, "race fact for new accepted turn during drain")).length, 1);
683
+ assert.equal(scheduler.snapshot().counters.durableOutputs, 1);
684
+ } finally {
685
+ release.resolve();
686
+ await scheduler.stop();
687
+ }
688
+ } finally {
689
+ await orchestrator?.destroy().catch(() => undefined);
690
+ await cleanupDir(memoryDir);
691
+ }
692
+ });
693
+
694
+
695
+
696
+ test("a drain tombstones every identical accepted occurrence before live-buffer reconciliation", async () => {
697
+ const memoryDir = await mkTempMemoryDir("buffer-reconcile-identical-turns");
698
+ let orchestrator: Orchestrator | undefined;
699
+ try {
700
+ orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
701
+ const buffer = orchestrator.buffer;
702
+ const key = "duplicate-session";
703
+ const duplicate = turn("identical accepted occurrence", key);
704
+ const newer = {
705
+ ...turn("new live turn after duplicate drain", key, "2026-11-05T00:00:00.000Z"),
706
+ acceptedCapture: true,
707
+ writeNamespaceOverride: orchestrator.config.defaultNamespace,
708
+ };
709
+ const claims = orchestrator.storage.bufferDrainClaims();
710
+ const now = new Date().toISOString();
711
+ const lease: DrainLease = {
712
+ ownerId: "duplicate-owner",
713
+ pid: process.pid,
714
+ host: hostname(),
715
+ acquiredAt: now,
716
+ heartbeatAt: now,
717
+ leaseMs: 60_000,
718
+ };
719
+
720
+ await buffer.addTurn(key, duplicate);
721
+ await buffer.addTurn(key, duplicate);
722
+ await buffer.flushPendingSave({ throwOnFailure: true });
723
+ assert.deepEqual(buffer.getTurns(key), [duplicate, duplicate]);
724
+ assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
725
+ const claimed = await claims.load(key);
726
+ assert.ok(claimed);
727
+ assert.deepEqual(claimed.entry.turns, [duplicate, duplicate]);
728
+
729
+ await claims.writeEntry(key, lease.ownerId, { turns: [], lastExtractionAt: null, extractionCount: 0 }, claimed.entry);
730
+ assert.deepEqual(await claims.drainedTurns(key), { turns: [duplicate, duplicate], retainedTurns: [] });
731
+ assert.equal(await claims.ack(key, lease.ownerId), "acked");
732
+
733
+ assert.notEqual(await buffer.addTurnBounded(key, newer), null);
734
+ await buffer.flushPendingSave({ throwOnFailure: true });
735
+ assert.deepEqual(buffer.getTurns(key), [newer]);
736
+ assert.deepEqual((await orchestrator.storage.loadBuffer()).entries?.[key]?.turns, [newer]);
737
+ } finally {
738
+ await orchestrator?.destroy().catch(() => undefined);
739
+ await cleanupDir(memoryDir);
740
+ }
741
+ });
742
+
743
+ test("ambiguous identical active and retained turns remain pending after cross-list progress", async () => {
744
+ const memoryDir = await mkTempMemoryDir("buffer-reconcile-cross-list-duplicates");
745
+ let orchestrator: Orchestrator | undefined;
746
+ try {
747
+ orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
748
+ const buffer = orchestrator.buffer;
749
+ const claims = orchestrator.storage.bufferDrainClaims();
750
+ const key = "cross-list-duplicates";
751
+ const same = turn("same occurrence identity in both lists", key);
752
+ const now = new Date().toISOString();
753
+ const lease: DrainLease = {
754
+ ownerId: "cross-list-owner",
755
+ pid: process.pid,
756
+ host: hostname(),
757
+ acquiredAt: now,
758
+ heartbeatAt: now,
759
+ leaseMs: 60_000,
760
+ };
761
+
762
+ await buffer.addTurn(key, same);
763
+ await buffer.retainDeferredTurns(key, [same], 10, [same]);
764
+ await buffer.flushPendingSave({ throwOnFailure: true });
765
+ assert.deepEqual(buffer.getTurns(key), [same, same]);
766
+ assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
767
+ const claim = await claims.load(key);
768
+ assert.ok(claim);
769
+ assert.deepEqual(claim.entry.turns, [same]);
770
+ assert.deepEqual(claim.entry.retainedTurns, [same]);
771
+
772
+ // One indistinguishable copy survives in the opposite list. Progress
773
+ // alone cannot prove whether that is the active or retained occurrence.
774
+ await claims.writeEntry(
775
+ key,
776
+ lease.ownerId,
777
+ { turns: [], retainedTurns: [same], lastExtractionAt: null, extractionCount: 0 },
778
+ claim.entry,
779
+ );
780
+ const drained = await claims.drainedTurnsForOwner(key, lease.ownerId);
781
+ assert.deepEqual(drained, { turns: [], retainedTurns: [] });
782
+ assert.deepEqual(
783
+ (await claims.load(key))?.entry,
784
+ { turns: [same], retainedTurns: [same], lastExtractionAt: null, extractionCount: 0 },
785
+ "both original source-list occurrences remain pending when provenance is ambiguous",
786
+ );
787
+ assert.ok(drained);
788
+ await buffer.save({ key, drained });
789
+ assert.deepEqual(buffer.getTurns(key), [same, same]);
790
+ } finally {
791
+ await orchestrator?.destroy().catch(() => undefined);
792
+ await cleanupDir(memoryDir);
793
+ }
794
+ });
795
+
796
+ test("claim tombstones clear identical active and retained copies on live save", async () => {
797
+ const memoryDir = await mkTempMemoryDir("buffer-reconcile-retained-provenance");
798
+ let orchestrator: Orchestrator | undefined;
799
+ try {
800
+ orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
801
+ const buffer = orchestrator.buffer;
802
+ const key = "retained-session";
803
+ const source = turn("same source identity", key);
804
+ const newer = {
805
+ ...turn("new live turn", key, "2026-11-04T00:00:00.000Z"),
806
+ acceptedCapture: true,
807
+ writeNamespaceOverride: orchestrator.config.defaultNamespace,
808
+ };
809
+ const claims = orchestrator.storage.bufferDrainClaims();
810
+ const now = new Date().toISOString();
811
+ const lease: DrainLease = {
812
+ ownerId: "retained-owner",
813
+ pid: process.pid,
814
+ host: hostname(),
815
+ acquiredAt: now,
816
+ heartbeatAt: now,
817
+ leaseMs: 60_000,
818
+ };
819
+
820
+ await buffer.addTurn(key, source);
821
+ await buffer.retainDeferredTurns(key, [source]);
822
+ assert.deepEqual(buffer.getTurns(key), [source, source]);
823
+ assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
824
+ const claimed = await claims.load(key);
825
+ assert.ok(claimed);
826
+ assert.deepEqual(claimed.entry, {
827
+ turns: [source],
828
+ retainedTurns: [source],
829
+ lastExtractionAt: null,
830
+ extractionCount: 0,
831
+ });
832
+ await claims.writeEntry(key, lease.ownerId, { turns: [], lastExtractionAt: null, extractionCount: 0 }, claimed.entry);
833
+ assert.deepEqual(await claims.drainedTurns(key), { turns: [source], retainedTurns: [source] });
834
+ assert.equal(await claims.ack(key, lease.ownerId), "acked");
835
+
836
+ // A new same-key save consumes the tombstone through BufferSessionFiles.
837
+ assert.notEqual(await buffer.addTurnBounded(key, newer), null);
838
+ await buffer.flushPendingSave({ throwOnFailure: true });
839
+ assert.deepEqual(buffer.getTurns(key), [newer]);
840
+ assert.deepEqual(buffer.getRetainedDeferredTurns(key), []);
841
+ const persisted = await orchestrator.storage.loadBuffer();
842
+ assert.deepEqual(persisted.entries?.[key]?.turns, [newer]);
843
+ assert.equal(persisted.entries?.[key]?.retainedTurns, undefined);
844
+ } finally {
845
+ await orchestrator?.destroy().catch(() => undefined);
846
+ await cleanupDir(memoryDir);
847
+ }
848
+ });
849
+ test("a lease takeover after the drained snapshot cannot erase newer scoped ingress", async () => {
850
+ const memoryDir = await mkTempMemoryDir("backlog-takeover-reconcile");
851
+ let orchestrator: Orchestrator | undefined;
852
+ try {
853
+ orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
854
+ const buffer = orchestrator.buffer;
855
+ const claims = orchestrator.storage.bufferDrainClaims();
856
+ const key = "takeover-session";
857
+ const oldTurn: BufferTurn = {
858
+ ...turn("old drained turn", key),
859
+ acceptedCapture: true,
860
+ writeNamespaceOverride: orchestrator.config.defaultNamespace,
861
+ };
862
+ const newerTurn: BufferTurn = {
863
+ ...turn("new accepted turn", key, "2026-11-03T00:00:00.000Z"),
864
+ acceptedCapture: true,
865
+ writeNamespaceOverride: orchestrator.config.defaultNamespace,
866
+ principalOverride: "new-owner-principal",
867
+ };
868
+ const lease = (ownerId: string): DrainLease => {
869
+ const now = new Date().toISOString();
870
+ return { ownerId, pid: process.pid, host: hostname(), acquiredAt: now, heartbeatAt: now, leaseMs: 60_000 };
871
+ };
872
+
873
+ assert.notEqual(await buffer.addTurnBounded(key, oldTurn), null);
874
+ await buffer.flushPendingSave({ throwOnFailure: true });
875
+ assert.equal(await claims.claimFromSessionFile(key, lease("owner-one")), "claimed");
876
+ const claimed = await claims.load(key);
877
+ assert.ok(claimed);
878
+ await claims.writeEntry(
879
+ key,
880
+ "owner-one",
881
+ { turns: [], lastExtractionAt: null, extractionCount: 0 },
882
+ claimed.entry
883
+ );
884
+ const ownerOneSnapshot = await claims.drainedTurnsForOwner(key, "owner-one");
885
+ assert.deepEqual(ownerOneSnapshot, { turns: [oldTurn], retainedTurns: [] });
886
+ assert.ok(ownerOneSnapshot);
887
+
888
+ // Simulate takeover after the old owner's fenced read but before its
889
+ // serialized live-buffer mutation. Progress is monotonic and identities
890
+ // are list-specific, so applying this snapshot cannot erase newer work.
891
+ await claims.releaseLease(key, "owner-one");
892
+ assert.equal(await claims.adopt(key, lease("owner-two")), "adopted");
893
+ assert.notEqual(await buffer.addTurnBounded(key, newerTurn), null);
894
+ await buffer.flushPendingSave({ throwOnFailure: true });
895
+ await buffer.save({ key, drained: ownerOneSnapshot });
896
+ assert.deepEqual(buffer.getTurns(key), [], "the old in-memory entry remains handed to the claim");
897
+
898
+ const afterIngress = await claims.load(key);
899
+ assert.deepEqual(afterIngress?.entry.turns, [newerTurn]);
900
+ assert.equal(afterIngress?.entry.turns[0]?.principalOverride, "new-owner-principal");
901
+ assert.equal(afterIngress?.entry.turns[0]?.writeNamespaceOverride, orchestrator.config.defaultNamespace);
902
+ } finally {
903
+ await orchestrator?.destroy().catch(() => undefined);
904
+ await cleanupDir(memoryDir);
905
+ }
906
+ });
467
907
  test("a claim decision unsettled mid-rename keeps the key reserved until it settles", async () => {
468
908
  const queue = new ExtractionQueueCoordinator(1);
469
909
  const state: StubState = {