@remnic/core 10.0.85 → 10.0.86

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 (417) hide show
  1. package/dist/access-admin-ops-surface.js +72 -71
  2. package/dist/access-authorization-probe.js +73 -72
  3. package/dist/access-boundary.d.ts.map +1 -1
  4. package/dist/access-boundary.js +72 -71
  5. package/dist/access-cli.js +157 -155
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-errors.d.ts +14 -0
  8. package/dist/access-errors.d.ts.map +1 -1
  9. package/dist/access-errors.js +5 -1
  10. package/dist/access-extraction-force-flush.js +72 -71
  11. package/dist/access-http-briefing.js +73 -72
  12. package/dist/access-http-query.js +74 -73
  13. package/dist/access-http.d.ts +12 -0
  14. package/dist/access-http.d.ts.map +1 -1
  15. package/dist/access-http.js +95 -94
  16. package/dist/access-identity-continuity-surface.js +72 -71
  17. package/dist/access-lcm-surface.js +72 -71
  18. package/dist/access-mcp.d.ts.map +1 -1
  19. package/dist/access-mcp.js +82 -81
  20. package/dist/access-memory-search-fanout.js +2 -2
  21. package/dist/access-namespace-preflight.js +72 -71
  22. package/dist/access-observe-write-category.js +10 -9
  23. package/dist/access-observe-write-surface.js +72 -71
  24. package/dist/access-offline-manifest.js +8 -8
  25. package/dist/access-operations-batch.js +73 -72
  26. package/dist/access-operations.js +80 -79
  27. package/dist/access-recall-concurrency.js +72 -71
  28. package/dist/access-recall-diagnostics.js +2 -2
  29. package/dist/access-recall-response.js +72 -71
  30. package/dist/access-recall-surface.js +72 -71
  31. package/dist/access-schema.d.ts.map +1 -1
  32. package/dist/access-schema.js +9 -8
  33. package/dist/access-service.d.ts +3 -3
  34. package/dist/access-service.d.ts.map +1 -1
  35. package/dist/access-service.js +72 -71
  36. package/dist/access-surface-catalog.js +74 -73
  37. package/dist/access-surface-catalog.js.map +1 -1
  38. package/dist/access-token-capabilities.js +2 -2
  39. package/dist/active-recall.js +32 -32
  40. package/dist/adapters/index.js +7 -7
  41. package/dist/adapters/registry.js +3 -3
  42. package/dist/briefing-deadline.d.ts +2 -2
  43. package/dist/briefing-deadline.d.ts.map +1 -1
  44. package/dist/briefing-deadline.js +1 -1
  45. package/dist/briefing.js +23 -23
  46. package/dist/buffer-admission.d.ts +22 -0
  47. package/dist/buffer-admission.d.ts.map +1 -0
  48. package/dist/buffer-admission.js +13 -0
  49. package/dist/buffer-eviction.d.ts +4 -5
  50. package/dist/buffer-eviction.d.ts.map +1 -1
  51. package/dist/buffer-eviction.js +1 -1
  52. package/dist/buffer-turn-helpers.d.ts +7 -0
  53. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  54. package/dist/buffer-turn-helpers.js +9 -1
  55. package/dist/buffer-types.d.ts +24 -1
  56. package/dist/buffer-types.d.ts.map +1 -1
  57. package/dist/buffer.d.ts +17 -18
  58. package/dist/buffer.d.ts.map +1 -1
  59. package/dist/buffer.js +12 -8
  60. package/dist/calibration.js +2 -2
  61. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  62. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  63. package/dist/capsule-import-gate.d.ts +11 -0
  64. package/dist/capsule-import-gate.d.ts.map +1 -0
  65. package/dist/capsule-import-gate.js +12 -0
  66. package/dist/capsule-import-gate.js.map +1 -0
  67. package/dist/causal-behavior.js +2 -2
  68. package/dist/causal-chain.js +2 -2
  69. package/dist/causal-consolidation.js +25 -25
  70. package/dist/causal-retrieval.js +2 -2
  71. package/dist/causal-trajectory.js +1 -1
  72. package/dist/{chunk-73YVMZ7D.js → chunk-2G4TV2ML.js} +11 -11
  73. package/dist/{chunk-BMIM6ESC.js → chunk-2JG2NI7C.js} +38 -38
  74. package/dist/{chunk-IR4C3DP2.js → chunk-2K3AUDH7.js} +2 -2
  75. package/dist/{chunk-6KFFRMPQ.js → chunk-2N4OUSIA.js} +2 -2
  76. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  77. package/dist/{chunk-44FQCC5D.js → chunk-2NUDHVYM.js} +2 -2
  78. package/dist/{chunk-FK4WC6VK.js → chunk-2U4LMSEX.js} +3 -3
  79. package/dist/{chunk-LT3CMO65.js → chunk-2XOFVTTT.js} +2 -2
  80. package/dist/{chunk-I6ORDT54.js → chunk-32CA7B6L.js} +2 -2
  81. package/dist/{chunk-TQVECGUQ.js → chunk-32YX7EJ3.js} +3 -3
  82. package/dist/{chunk-H4D5YURJ.js → chunk-3CTKEXXF.js} +120 -118
  83. package/dist/{chunk-H4D5YURJ.js.map → chunk-3CTKEXXF.js.map} +1 -1
  84. package/dist/{chunk-HLWXCDUQ.js → chunk-3DAWHP5K.js} +5 -5
  85. package/dist/{chunk-GBOYOL5L.js → chunk-3PJEDHLD.js} +2 -2
  86. package/dist/{chunk-ZWTHVUT5.js → chunk-3UZ2VMKD.js} +2 -2
  87. package/dist/{chunk-2XYDL2FG.js → chunk-3Y5X6YFH.js} +2 -2
  88. package/dist/{chunk-23B4HLWG.js → chunk-4GMI5HFH.js} +9 -9
  89. package/dist/{chunk-AP3TSV6S.js → chunk-4LCVA2LW.js} +3 -3
  90. package/dist/{chunk-MZCGHPIJ.js → chunk-4X7HQKAG.js} +2 -2
  91. package/dist/{chunk-YNBMBL4C.js → chunk-4XYAWK5H.js} +2 -2
  92. package/dist/{chunk-2JAT4S7N.js → chunk-5SOSQTVE.js} +2 -2
  93. package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
  94. package/dist/{chunk-NIV7RPWQ.js → chunk-6BCWFCDG.js} +7 -7
  95. package/dist/{chunk-V27FK6D6.js → chunk-6XTUZKCZ.js} +2 -2
  96. package/dist/{chunk-KASJJGRG.js → chunk-6ZELOK6O.js} +3 -8
  97. package/dist/chunk-6ZELOK6O.js.map +1 -0
  98. package/dist/{chunk-2ASEDFOA.js → chunk-73UZZFJC.js} +3 -3
  99. package/dist/{chunk-UHPHH74F.js → chunk-77PDJ4OE.js} +3 -3
  100. package/dist/{chunk-BATPQNRG.js → chunk-7GSL4LBG.js} +2 -2
  101. package/dist/{chunk-B6SPPYGJ.js → chunk-7IWRLYFJ.js} +2 -2
  102. package/dist/{chunk-BUKK5SWA.js → chunk-7LCGFUMB.js} +4 -4
  103. package/dist/{chunk-ZXF3O7P4.js → chunk-ARRTLPRC.js} +2 -2
  104. package/dist/{chunk-S5CQI2KF.js → chunk-B3GKQ6DU.js} +2 -2
  105. package/dist/{chunk-2URKYLMK.js → chunk-B56LORLY.js} +2 -2
  106. package/dist/{chunk-4IWEPQEV.js → chunk-BIVKPQ6Q.js} +2 -2
  107. package/dist/{chunk-DM4RRTN7.js → chunk-BLSHFIR6.js} +7 -6
  108. package/dist/chunk-BLSHFIR6.js.map +1 -0
  109. package/dist/{chunk-4DPLCYEK.js → chunk-C2HORX76.js} +3 -3
  110. package/dist/{chunk-ELWI2R6H.js → chunk-CC4V7HNP.js} +22 -22
  111. package/dist/{chunk-LGEH473S.js → chunk-CJCTKL6Y.js} +9 -1
  112. package/dist/chunk-CJCTKL6Y.js.map +1 -0
  113. package/dist/{chunk-WFDWLWDK.js → chunk-CN7Q3T2J.js} +4 -4
  114. package/dist/{chunk-GJVGFKOU.js → chunk-CYCEROPR.js} +3 -3
  115. package/dist/{chunk-CCU7URRP.js → chunk-D5EV6Y2L.js} +3 -3
  116. package/dist/{chunk-N6II66E5.js → chunk-DT7SNJH6.js} +3 -3
  117. package/dist/{chunk-FRM4UTDR.js → chunk-DZ7DPDVW.js} +8 -6
  118. package/dist/chunk-DZ7DPDVW.js.map +1 -0
  119. package/dist/{chunk-MVOJDLXS.js → chunk-EA2LA5RI.js} +2 -2
  120. package/dist/{chunk-WBN66HPI.js → chunk-ELHJK7LM.js} +6 -6
  121. package/dist/{chunk-GKYIRTNX.js → chunk-EQ4U73UH.js} +45 -14
  122. package/dist/chunk-EQ4U73UH.js.map +1 -0
  123. package/dist/{chunk-ONN2E5QD.js → chunk-EUBDYOLD.js} +6 -6
  124. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  125. package/dist/{chunk-3LHPTBWG.js → chunk-F74G4KIH.js} +2 -2
  126. package/dist/{chunk-X7REO4CH.js → chunk-FF3EFLUT.js} +7 -5
  127. package/dist/chunk-FF3EFLUT.js.map +1 -0
  128. package/dist/chunk-FKBGK3NC.js +62 -0
  129. package/dist/chunk-FKBGK3NC.js.map +1 -0
  130. package/dist/{chunk-X2KO4FK5.js → chunk-FNFURZBZ.js} +5 -5
  131. package/dist/chunk-FNFURZBZ.js.map +1 -0
  132. package/dist/{chunk-MYCCO6AA.js → chunk-G54BOKZ6.js} +2 -2
  133. package/dist/{chunk-JFH6JAGY.js → chunk-G67EUQAB.js} +5 -5
  134. package/dist/{chunk-TV65NR7L.js → chunk-GULUWOA5.js} +2 -2
  135. package/dist/{chunk-6DANGCMO.js → chunk-HFUKXOUW.js} +2 -2
  136. package/dist/{chunk-Y3IYKNBY.js → chunk-HLN2HOXX.js} +2 -2
  137. package/dist/{chunk-Y43X3E24.js → chunk-IBDWYRDR.js} +3 -3
  138. package/dist/{chunk-XSFQN7TP.js → chunk-IBGU6AN6.js} +15 -14
  139. package/dist/chunk-IBGU6AN6.js.map +1 -0
  140. package/dist/{chunk-AJG2TAMR.js → chunk-IEEV27BC.js} +2 -2
  141. package/dist/{chunk-FZPDRCOB.js → chunk-IJ3OEECW.js} +4 -4
  142. package/dist/{chunk-D6THW7PA.js → chunk-IPD5GC7C.js} +2 -2
  143. package/dist/{chunk-VCVEMQJK.js → chunk-JM2YZKCQ.js} +2 -2
  144. package/dist/{chunk-H34INI3P.js → chunk-KVSOEVAF.js} +34 -11
  145. package/dist/chunk-KVSOEVAF.js.map +1 -0
  146. package/dist/chunk-M3BEJJNV.js +31 -0
  147. package/dist/chunk-M3BEJJNV.js.map +1 -0
  148. package/dist/{chunk-OSTFDWZJ.js → chunk-NIVKG3DS.js} +4 -4
  149. package/dist/{chunk-VZELUMKK.js → chunk-NQP4ZYGG.js} +2 -2
  150. package/dist/{chunk-3ODZLCHC.js → chunk-NT7F7WJQ.js} +2 -2
  151. package/dist/{chunk-YO2JEX2Z.js → chunk-OGBQMY46.js} +2 -2
  152. package/dist/{chunk-O55AGDTI.js → chunk-OL553AWT.js} +10 -10
  153. package/dist/{chunk-CLVILSBR.js → chunk-OZNBZJ3S.js} +22 -22
  154. package/dist/{chunk-RNGDKFY2.js → chunk-P452SEKO.js} +2 -2
  155. package/dist/{chunk-JX3E4XCW.js → chunk-PDOOUXCS.js} +2 -2
  156. package/dist/{chunk-V45TEVDA.js → chunk-PKZ274PI.js} +3 -3
  157. package/dist/{chunk-MNYQVYJ4.js → chunk-QD5R6U5J.js} +2 -2
  158. package/dist/{chunk-DI5ET2IE.js → chunk-QEZ3DW5O.js} +9 -9
  159. package/dist/{chunk-WYPCJK6K.js → chunk-QKERFEJS.js} +2 -2
  160. package/dist/{chunk-BLMTOIBK.js → chunk-QKES7GU6.js} +2 -2
  161. package/dist/{chunk-PCQWKMOL.js → chunk-QNSZYTSG.js} +2 -2
  162. package/dist/{chunk-433EXTC4.js → chunk-ROGO7FRA.js} +5 -4
  163. package/dist/{chunk-433EXTC4.js.map → chunk-ROGO7FRA.js.map} +1 -1
  164. package/dist/{chunk-MHSBOEH7.js → chunk-ROKDHO74.js} +2 -2
  165. package/dist/{chunk-BLGQUZFB.js → chunk-RWGRSKN5.js} +7 -5
  166. package/dist/chunk-RWGRSKN5.js.map +1 -0
  167. package/dist/{chunk-3VU2FDTU.js → chunk-SYKJBS7S.js} +10 -10
  168. package/dist/{chunk-5BE3FLKL.js → chunk-TAUHAP5J.js} +4 -4
  169. package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
  170. package/dist/{chunk-SGHV6LZ7.js → chunk-TQ4SCPGQ.js} +67 -51
  171. package/dist/chunk-TQ4SCPGQ.js.map +1 -0
  172. package/dist/{chunk-E7V5AK7I.js → chunk-TRMZHGIX.js} +8 -8
  173. package/dist/{chunk-WBDIZWJQ.js → chunk-TSS3D7ZY.js} +2 -2
  174. package/dist/{chunk-HVFX2N2G.js → chunk-UAUV3BWS.js} +65 -83
  175. package/dist/chunk-UAUV3BWS.js.map +1 -0
  176. package/dist/{chunk-OMYRPMQC.js → chunk-US7KPQA5.js} +2 -2
  177. package/dist/{chunk-KHZVAISH.js → chunk-UWRHHRDK.js} +2 -2
  178. package/dist/{chunk-WLNWFHX3.js → chunk-UXXK523X.js} +40 -2
  179. package/dist/{chunk-WLNWFHX3.js.map → chunk-UXXK523X.js.map} +1 -1
  180. package/dist/{chunk-SDESKFKT.js → chunk-UYHDMCY2.js} +2 -2
  181. package/dist/{chunk-YAAKVTZA.js → chunk-VKILERSL.js} +2 -2
  182. package/dist/{chunk-2TGIKMMZ.js → chunk-WXDTOE6F.js} +2 -2
  183. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  184. package/dist/{chunk-P225W4RQ.js → chunk-XU37EJTW.js} +22 -22
  185. package/dist/chunk-XU37EJTW.js.map +1 -0
  186. package/dist/{chunk-QA3Y2O6H.js → chunk-YAVIBVCU.js} +2 -2
  187. package/dist/{chunk-CMRHXPLQ.js → chunk-YL4UKMD7.js} +117 -51
  188. package/dist/chunk-YL4UKMD7.js.map +1 -0
  189. package/dist/{chunk-GYNRNKMW.js → chunk-YMJRLT7S.js} +28 -8
  190. package/dist/chunk-YMJRLT7S.js.map +1 -0
  191. package/dist/{chunk-E5QEF3JO.js → chunk-YRZPBN46.js} +3 -3
  192. package/dist/{chunk-FMFCQRDM.js → chunk-ZANNIXXE.js} +3 -3
  193. package/dist/{chunk-3KGCHTTK.js → chunk-ZBFLV657.js} +3 -3
  194. package/dist/{chunk-VEFGHRKV.js → chunk-ZOJUWVAU.js} +1 -1
  195. package/dist/cli.js +117 -116
  196. package/dist/coding/export-okf-codegraph.js +21 -21
  197. package/dist/coding/pre-action-gate.js +2 -2
  198. package/dist/compounding/engine.js +23 -23
  199. package/dist/config.js +32 -32
  200. package/dist/connectors/codex-materialize-runner.js +22 -22
  201. package/dist/connectors/index.js +26 -26
  202. package/dist/convergence-refresh.js +24 -24
  203. package/dist/conversation-index/backend.js +2 -2
  204. package/dist/corpus-watermark.js +26 -26
  205. package/dist/dashboard-runtime.js +2 -2
  206. package/dist/deep-recall-config.js +2 -2
  207. package/dist/deep-recall-http-glue.js +11 -11
  208. package/dist/entity-retrieval.js +23 -23
  209. package/dist/explicit-capture.js +1 -1
  210. package/dist/external-wiki-access.js +73 -72
  211. package/dist/extraction-quote-grounding.d.ts +5 -1
  212. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  213. package/dist/extraction-quote-grounding.js +1 -1
  214. package/dist/extraction-source-grounding-rules.d.ts +3 -1
  215. package/dist/extraction-source-grounding-rules.d.ts.map +1 -1
  216. package/dist/extraction-source-grounding-rules.js +2 -2
  217. package/dist/extraction-source-grounding.d.ts +1 -1
  218. package/dist/extraction-source-grounding.d.ts.map +1 -1
  219. package/dist/extraction-source-grounding.js +3 -3
  220. package/dist/extraction.d.ts.map +1 -1
  221. package/dist/extraction.js +40 -40
  222. package/dist/harmonic-retrieval.js +22 -22
  223. package/dist/index.js +196 -194
  224. package/dist/lcm/index.js +3 -3
  225. package/dist/local-llm.js +2 -2
  226. package/dist/location/index.js +25 -25
  227. package/dist/maintenance/memory-governance.js +22 -22
  228. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +22 -22
  229. package/dist/maintenance/rebuild-memory-projection-runner.js +24 -24
  230. package/dist/maintenance/rebuild-memory-projection.js +23 -23
  231. package/dist/memory-browse-http-glue.js +2 -2
  232. package/dist/memory-browse-operations.js +73 -72
  233. package/dist/memory-browse.js +5 -5
  234. package/dist/namespaces/migrate.js +23 -23
  235. package/dist/namespaces/search.d.ts +1 -0
  236. package/dist/namespaces/search.d.ts.map +1 -1
  237. package/dist/namespaces/search.js +10 -8
  238. package/dist/namespaces/storage.js +22 -22
  239. package/dist/offline-sync-changeset-generations.js +3 -3
  240. package/dist/offline-sync-embedding-generation.js +2 -2
  241. package/dist/offline-sync-impression-drain.js +2 -2
  242. package/dist/offline-sync-snapshot-stream.js +6 -6
  243. package/dist/offline-sync.js +5 -5
  244. package/dist/operator-doctor-corpus.js +29 -29
  245. package/dist/operator-doctor-replica.js +4 -4
  246. package/dist/operator-toolkit.js +44 -44
  247. package/dist/orchestration/backlog-scheduler.js +158 -156
  248. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  249. package/dist/orchestration/buffer-drain-runner.js +158 -156
  250. package/dist/orchestration/maintenance.js +36 -36
  251. package/dist/orchestration/turn-ingestion.d.ts +2 -9
  252. package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
  253. package/dist/orchestrator.js +159 -157
  254. package/dist/qmd.js +4 -4
  255. package/dist/recall-navigation-http-glue.js +2 -2
  256. package/dist/recall-navigation-operations.js +73 -72
  257. package/dist/recall-navigation.js +72 -71
  258. package/dist/recall-state-view-admission.js +5 -5
  259. package/dist/recall-why-service.js +5 -5
  260. package/dist/replica-divergence.js +3 -3
  261. package/dist/resume-bundles.js +33 -33
  262. package/dist/search/factory.js +7 -7
  263. package/dist/search/index.js +11 -11
  264. package/dist/semantic-consolidation.js +23 -23
  265. package/dist/semantic-rule-promotion.js +22 -22
  266. package/dist/semantic-rule-verifier.js +22 -22
  267. package/dist/source-agent-qualifier.d.ts +2 -0
  268. package/dist/source-agent-qualifier.d.ts.map +1 -1
  269. package/dist/source-agent-qualifier.js +33 -33
  270. package/dist/standup/build.js +23 -23
  271. package/dist/storage/buffer-drain-claims.js +2 -2
  272. package/dist/storage/buffer-session-files.js +2 -2
  273. package/dist/storage.js +23 -23
  274. package/dist/summarizer.js +3 -3
  275. package/dist/support-passport/index.js +75 -74
  276. package/dist/tokens.js +3 -3
  277. package/dist/transfer/backup.js +2 -2
  278. package/dist/transfer/capsule-export.js +2 -2
  279. package/dist/transfer/capsule-import.js +2 -2
  280. package/dist/transfer/export-okf.js +22 -22
  281. package/dist/verified-recall.js +22 -22
  282. package/dist/who-knows-cli.js +24 -24
  283. package/dist/who-knows.js +23 -23
  284. package/package.json +2 -2
  285. package/src/access-boundary.ts +15 -0
  286. package/src/access-errors.ts +22 -0
  287. package/src/access-fleet-wide-work-peer.test.ts +236 -0
  288. package/src/access-http-peer-fleet-wide.test.ts +192 -0
  289. package/src/access-http.test.ts +249 -4
  290. package/src/access-http.ts +114 -39
  291. package/src/access-mcp.test.ts +64 -0
  292. package/src/access-mcp.ts +17 -7
  293. package/src/access-observe-replay-durability.test.ts +40 -4
  294. package/src/access-operations-batch.ts +23 -12
  295. package/src/access-proto-injection.test.ts +340 -0
  296. package/src/access-schema.ts +31 -5
  297. package/src/access-service-capsule-import.test.ts +235 -0
  298. package/src/access-service.ts +6 -45
  299. package/src/briefing-deadline.ts +5 -4
  300. package/src/buffer-admission.ts +45 -0
  301. package/src/buffer-eviction-backpressure.test.ts +211 -0
  302. package/src/buffer-eviction.ts +8 -14
  303. package/src/buffer-incremental-persistence.test.ts +25 -37
  304. package/src/buffer-session.test.ts +32 -16
  305. package/src/buffer-turn-helpers.ts +41 -0
  306. package/src/buffer-types.ts +28 -1
  307. package/src/buffer.ts +77 -85
  308. package/src/capsule-import-gate.ts +109 -0
  309. package/src/extraction-quote-grounding.test.ts +76 -0
  310. package/src/extraction-quote-grounding.ts +49 -13
  311. package/src/extraction-source-grounding-rules.ts +6 -1
  312. package/src/extraction-source-grounding.ts +2 -0
  313. package/src/extraction.ts +3 -4
  314. package/src/namespaces/search.test.ts +141 -1
  315. package/src/namespaces/search.ts +39 -9
  316. package/src/orchestration/turn-ingestion.ts +38 -39
  317. package/src/recall-navigation-operations.ts +9 -2
  318. package/src/source-agent-qualifier.ts +3 -0
  319. package/dist/chunk-BLGQUZFB.js.map +0 -1
  320. package/dist/chunk-CMRHXPLQ.js.map +0 -1
  321. package/dist/chunk-DM4RRTN7.js.map +0 -1
  322. package/dist/chunk-FRM4UTDR.js.map +0 -1
  323. package/dist/chunk-GKYIRTNX.js.map +0 -1
  324. package/dist/chunk-GYNRNKMW.js.map +0 -1
  325. package/dist/chunk-H34INI3P.js.map +0 -1
  326. package/dist/chunk-HVFX2N2G.js.map +0 -1
  327. package/dist/chunk-KASJJGRG.js.map +0 -1
  328. package/dist/chunk-LGEH473S.js.map +0 -1
  329. package/dist/chunk-P225W4RQ.js.map +0 -1
  330. package/dist/chunk-SGHV6LZ7.js.map +0 -1
  331. package/dist/chunk-X2KO4FK5.js.map +0 -1
  332. package/dist/chunk-X7REO4CH.js.map +0 -1
  333. package/dist/chunk-XSFQN7TP.js.map +0 -1
  334. /package/dist/{capsule-crypto-GWVG7LGC.js.map → buffer-admission.js.map} +0 -0
  335. /package/dist/{chunk-73YVMZ7D.js.map → chunk-2G4TV2ML.js.map} +0 -0
  336. /package/dist/{chunk-BMIM6ESC.js.map → chunk-2JG2NI7C.js.map} +0 -0
  337. /package/dist/{chunk-IR4C3DP2.js.map → chunk-2K3AUDH7.js.map} +0 -0
  338. /package/dist/{chunk-6KFFRMPQ.js.map → chunk-2N4OUSIA.js.map} +0 -0
  339. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  340. /package/dist/{chunk-44FQCC5D.js.map → chunk-2NUDHVYM.js.map} +0 -0
  341. /package/dist/{chunk-FK4WC6VK.js.map → chunk-2U4LMSEX.js.map} +0 -0
  342. /package/dist/{chunk-LT3CMO65.js.map → chunk-2XOFVTTT.js.map} +0 -0
  343. /package/dist/{chunk-I6ORDT54.js.map → chunk-32CA7B6L.js.map} +0 -0
  344. /package/dist/{chunk-TQVECGUQ.js.map → chunk-32YX7EJ3.js.map} +0 -0
  345. /package/dist/{chunk-HLWXCDUQ.js.map → chunk-3DAWHP5K.js.map} +0 -0
  346. /package/dist/{chunk-GBOYOL5L.js.map → chunk-3PJEDHLD.js.map} +0 -0
  347. /package/dist/{chunk-ZWTHVUT5.js.map → chunk-3UZ2VMKD.js.map} +0 -0
  348. /package/dist/{chunk-2XYDL2FG.js.map → chunk-3Y5X6YFH.js.map} +0 -0
  349. /package/dist/{chunk-23B4HLWG.js.map → chunk-4GMI5HFH.js.map} +0 -0
  350. /package/dist/{chunk-AP3TSV6S.js.map → chunk-4LCVA2LW.js.map} +0 -0
  351. /package/dist/{chunk-MZCGHPIJ.js.map → chunk-4X7HQKAG.js.map} +0 -0
  352. /package/dist/{chunk-YNBMBL4C.js.map → chunk-4XYAWK5H.js.map} +0 -0
  353. /package/dist/{chunk-2JAT4S7N.js.map → chunk-5SOSQTVE.js.map} +0 -0
  354. /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
  355. /package/dist/{chunk-NIV7RPWQ.js.map → chunk-6BCWFCDG.js.map} +0 -0
  356. /package/dist/{chunk-V27FK6D6.js.map → chunk-6XTUZKCZ.js.map} +0 -0
  357. /package/dist/{chunk-2ASEDFOA.js.map → chunk-73UZZFJC.js.map} +0 -0
  358. /package/dist/{chunk-UHPHH74F.js.map → chunk-77PDJ4OE.js.map} +0 -0
  359. /package/dist/{chunk-BATPQNRG.js.map → chunk-7GSL4LBG.js.map} +0 -0
  360. /package/dist/{chunk-B6SPPYGJ.js.map → chunk-7IWRLYFJ.js.map} +0 -0
  361. /package/dist/{chunk-BUKK5SWA.js.map → chunk-7LCGFUMB.js.map} +0 -0
  362. /package/dist/{chunk-ZXF3O7P4.js.map → chunk-ARRTLPRC.js.map} +0 -0
  363. /package/dist/{chunk-S5CQI2KF.js.map → chunk-B3GKQ6DU.js.map} +0 -0
  364. /package/dist/{chunk-2URKYLMK.js.map → chunk-B56LORLY.js.map} +0 -0
  365. /package/dist/{chunk-4IWEPQEV.js.map → chunk-BIVKPQ6Q.js.map} +0 -0
  366. /package/dist/{chunk-4DPLCYEK.js.map → chunk-C2HORX76.js.map} +0 -0
  367. /package/dist/{chunk-ELWI2R6H.js.map → chunk-CC4V7HNP.js.map} +0 -0
  368. /package/dist/{chunk-WFDWLWDK.js.map → chunk-CN7Q3T2J.js.map} +0 -0
  369. /package/dist/{chunk-GJVGFKOU.js.map → chunk-CYCEROPR.js.map} +0 -0
  370. /package/dist/{chunk-CCU7URRP.js.map → chunk-D5EV6Y2L.js.map} +0 -0
  371. /package/dist/{chunk-N6II66E5.js.map → chunk-DT7SNJH6.js.map} +0 -0
  372. /package/dist/{chunk-MVOJDLXS.js.map → chunk-EA2LA5RI.js.map} +0 -0
  373. /package/dist/{chunk-WBN66HPI.js.map → chunk-ELHJK7LM.js.map} +0 -0
  374. /package/dist/{chunk-ONN2E5QD.js.map → chunk-EUBDYOLD.js.map} +0 -0
  375. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  376. /package/dist/{chunk-3LHPTBWG.js.map → chunk-F74G4KIH.js.map} +0 -0
  377. /package/dist/{chunk-MYCCO6AA.js.map → chunk-G54BOKZ6.js.map} +0 -0
  378. /package/dist/{chunk-JFH6JAGY.js.map → chunk-G67EUQAB.js.map} +0 -0
  379. /package/dist/{chunk-TV65NR7L.js.map → chunk-GULUWOA5.js.map} +0 -0
  380. /package/dist/{chunk-6DANGCMO.js.map → chunk-HFUKXOUW.js.map} +0 -0
  381. /package/dist/{chunk-Y3IYKNBY.js.map → chunk-HLN2HOXX.js.map} +0 -0
  382. /package/dist/{chunk-Y43X3E24.js.map → chunk-IBDWYRDR.js.map} +0 -0
  383. /package/dist/{chunk-AJG2TAMR.js.map → chunk-IEEV27BC.js.map} +0 -0
  384. /package/dist/{chunk-FZPDRCOB.js.map → chunk-IJ3OEECW.js.map} +0 -0
  385. /package/dist/{chunk-D6THW7PA.js.map → chunk-IPD5GC7C.js.map} +0 -0
  386. /package/dist/{chunk-VCVEMQJK.js.map → chunk-JM2YZKCQ.js.map} +0 -0
  387. /package/dist/{chunk-OSTFDWZJ.js.map → chunk-NIVKG3DS.js.map} +0 -0
  388. /package/dist/{chunk-VZELUMKK.js.map → chunk-NQP4ZYGG.js.map} +0 -0
  389. /package/dist/{chunk-3ODZLCHC.js.map → chunk-NT7F7WJQ.js.map} +0 -0
  390. /package/dist/{chunk-YO2JEX2Z.js.map → chunk-OGBQMY46.js.map} +0 -0
  391. /package/dist/{chunk-O55AGDTI.js.map → chunk-OL553AWT.js.map} +0 -0
  392. /package/dist/{chunk-CLVILSBR.js.map → chunk-OZNBZJ3S.js.map} +0 -0
  393. /package/dist/{chunk-RNGDKFY2.js.map → chunk-P452SEKO.js.map} +0 -0
  394. /package/dist/{chunk-JX3E4XCW.js.map → chunk-PDOOUXCS.js.map} +0 -0
  395. /package/dist/{chunk-V45TEVDA.js.map → chunk-PKZ274PI.js.map} +0 -0
  396. /package/dist/{chunk-MNYQVYJ4.js.map → chunk-QD5R6U5J.js.map} +0 -0
  397. /package/dist/{chunk-DI5ET2IE.js.map → chunk-QEZ3DW5O.js.map} +0 -0
  398. /package/dist/{chunk-WYPCJK6K.js.map → chunk-QKERFEJS.js.map} +0 -0
  399. /package/dist/{chunk-BLMTOIBK.js.map → chunk-QKES7GU6.js.map} +0 -0
  400. /package/dist/{chunk-PCQWKMOL.js.map → chunk-QNSZYTSG.js.map} +0 -0
  401. /package/dist/{chunk-MHSBOEH7.js.map → chunk-ROKDHO74.js.map} +0 -0
  402. /package/dist/{chunk-3VU2FDTU.js.map → chunk-SYKJBS7S.js.map} +0 -0
  403. /package/dist/{chunk-5BE3FLKL.js.map → chunk-TAUHAP5J.js.map} +0 -0
  404. /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
  405. /package/dist/{chunk-E7V5AK7I.js.map → chunk-TRMZHGIX.js.map} +0 -0
  406. /package/dist/{chunk-WBDIZWJQ.js.map → chunk-TSS3D7ZY.js.map} +0 -0
  407. /package/dist/{chunk-OMYRPMQC.js.map → chunk-US7KPQA5.js.map} +0 -0
  408. /package/dist/{chunk-KHZVAISH.js.map → chunk-UWRHHRDK.js.map} +0 -0
  409. /package/dist/{chunk-SDESKFKT.js.map → chunk-UYHDMCY2.js.map} +0 -0
  410. /package/dist/{chunk-YAAKVTZA.js.map → chunk-VKILERSL.js.map} +0 -0
  411. /package/dist/{chunk-2TGIKMMZ.js.map → chunk-WXDTOE6F.js.map} +0 -0
  412. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  413. /package/dist/{chunk-QA3Y2O6H.js.map → chunk-YAVIBVCU.js.map} +0 -0
  414. /package/dist/{chunk-E5QEF3JO.js.map → chunk-YRZPBN46.js.map} +0 -0
  415. /package/dist/{chunk-FMFCQRDM.js.map → chunk-ZANNIXXE.js.map} +0 -0
  416. /package/dist/{chunk-3KGCHTTK.js.map → chunk-ZBFLV657.js.map} +0 -0
  417. /package/dist/{chunk-VEFGHRKV.js.map → chunk-ZOJUWVAU.js.map} +0 -0
@@ -1,9 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
- import { constants as fsConstants } from "node:fs";
3
2
  import { readdirSync, unlinkSync } from "node:fs";
4
3
  import { stat } from "node:fs/promises";
5
4
  import * as nodeFs from "node:fs/promises";
6
- import { ZodError } from "zod";
7
5
  import { summarizeHourlyStatus } from "./summarizer.js";
8
6
  import { decodeCitationNamespace } from "./access-citation-namespace.js";
9
7
  import {
@@ -288,6 +286,7 @@ import {
288
286
  } from "./capabilities.js";
289
287
  import { resolveRecallEnhancementCapabilities } from "./capabilities.js";
290
288
  import { type CapsuleListEntry, defaultCapsulesDir } from "./capsule-cli.js";
289
+ import { isCapsuleImportArchiveInputError, validateCapsuleImportArchive } from "./capsule-import-gate.js";
291
290
  import type { SemanticDedupLookup } from "./dedup/semantic.js";
292
291
  import type { FallbackLlmClient } from "./fallback-llm.js";
293
292
  import type { LocalLlmClient } from "./local-llm.js";
@@ -5366,6 +5365,9 @@ export class EngramAccessService extends SupportPassportAccessServiceBase {
5366
5365
  * `versioning` defaults to the orchestrator's page-versioning config so
5367
5366
  * `mode: "overwrite"` automatically snapshots prior content without the
5368
5367
  * caller having to construct the config object.
5368
+ *
5369
+ * Remote callers (HTTP and MCP) may only import from the destination
5370
+ * namespace's capsule store; the CLI keeps unrestricted local paths.
5369
5371
  */
5370
5372
  async capsuleImport(
5371
5373
  opts: Omit<ImportCapsuleOptions, "root" | "memoryDir"> & {
@@ -5385,59 +5387,18 @@ export class EngramAccessService extends SupportPassportAccessServiceBase {
5385
5387
  maxVersionsPerPage: this.orchestrator.config.versioningMaxPerPage,
5386
5388
  sidecarDir: this.orchestrator.config.versioningSidecarDir,
5387
5389
  };
5388
- await this.validateCapsuleImportArchivePath(importOptions.archivePath);
5390
+ await validateCapsuleImportArchive(importOptions.archivePath, storage.dir);
5389
5391
  try {
5390
5392
  return await importCapsuleFn({ ...importOptions, root, memoryDir, versioning });
5391
5393
  } catch (err) {
5392
5394
  const message = err instanceof Error ? err.message : String(err);
5393
- if (this.isCapsuleImportArchiveInputError(err, message)) {
5395
+ if (isCapsuleImportArchiveInputError(err, message)) {
5394
5396
  throw new EngramAccessInputError(`capsule import failed: ${message}`);
5395
5397
  }
5396
5398
  throw err;
5397
5399
  }
5398
5400
  }
5399
5401
 
5400
- private async validateCapsuleImportArchivePath(archivePath: string): Promise<void> {
5401
- let archiveStat;
5402
- try {
5403
- archiveStat = await stat(archivePath);
5404
- } catch (err) {
5405
- if (!this.isCapsuleImportPathInputFsError(err)) throw err;
5406
- const message = err instanceof Error ? err.message : String(err);
5407
- throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
5408
- }
5409
- if (!archiveStat.isFile()) {
5410
- throw new EngramAccessInputError("capsule import failed: archivePath must point to a file");
5411
- }
5412
- try {
5413
- await nodeFs.access(archivePath, fsConstants.R_OK);
5414
- } catch (err) {
5415
- if (!this.isCapsuleImportPathInputFsError(err)) throw err;
5416
- const message = err instanceof Error ? err.message : String(err);
5417
- throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
5418
- }
5419
- }
5420
-
5421
- private isCapsuleImportPathInputFsError(err: unknown): boolean {
5422
- const code =
5423
- typeof err === "object" && err !== null && "code" in err ? (err as { code?: unknown }).code : undefined;
5424
- return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP";
5425
- }
5426
-
5427
- private isCapsuleImportArchiveInputError(err: unknown, message: string): boolean {
5428
- if (err instanceof ZodError) return true;
5429
- const code =
5430
- typeof err === "object" && err !== null && "code" in err ? (err as { code?: unknown }).code : undefined;
5431
- if (typeof code === "string" && code.startsWith("Z_")) return true;
5432
- return (
5433
- message.startsWith("importCapsule: archive") ||
5434
- message.startsWith("importCapsule: bundle") ||
5435
- message.startsWith("importCapsule: manifest") ||
5436
- message.startsWith("importCapsule: record") ||
5437
- /incorrect header check|invalid stored block lengths|not in gzip format|unexpected end of file/i.test(message)
5438
- );
5439
- }
5440
-
5441
5402
  /**
5442
5403
  * Export a capsule archive from the orchestrator's memory directory.
5443
5404
  *
@@ -3,20 +3,21 @@ import { isAbortError, raceAbort } from "./abort-error.js";
3
3
  export const BRIEFING_DEADLINE_MS = 30_000;
4
4
 
5
5
  export class BriefingTimeoutError extends Error {
6
- constructor(readonly deadlineMs: number) {
7
- super(`briefing timed out after ${deadlineMs}ms`);
6
+ constructor(readonly deadlineMs: number, label = "briefing") {
7
+ super(`${label} timed out after ${deadlineMs}ms`);
8
8
  this.name = "TimeoutError";
9
9
  }
10
10
  }
11
11
 
12
12
  export async function withBriefingDeadline<T>(
13
13
  run: (signal: AbortSignal) => Promise<T>,
14
- abortSignal?: AbortSignal
14
+ abortSignal?: AbortSignal,
15
+ label = "briefing",
15
16
  ): Promise<T> {
16
17
  const controller = new AbortController();
17
18
  const signal = abortSignal ? AbortSignal.any([abortSignal, controller.signal]) : controller.signal;
18
19
  const timer = setTimeout(
19
- () => controller.abort(new BriefingTimeoutError(BRIEFING_DEADLINE_MS)),
20
+ () => controller.abort(new BriefingTimeoutError(BRIEFING_DEADLINE_MS, label)),
20
21
  BRIEFING_DEADLINE_MS
21
22
  );
22
23
  try {
@@ -0,0 +1,45 @@
1
+ /** Buffer session-entry admission: refusal errors and the live-admission probe. */
2
+
3
+ import { selectEvictedBufferKeys, type BufferEvictionHooks } from "./buffer-eviction.js";
4
+ import type { BufferEntryState } from "./types.js";
5
+
6
+ /**
7
+ * Raised when a NEW live-capture session cannot be admitted because the
8
+ * session-entry cap is full of non-evictable backlog. The turn was never
9
+ * accepted; the request is retryable once extraction drains an entry.
10
+ */
11
+ export class BufferAdmissionRefusedError extends Error {
12
+ constructor(bufferKey: string, maxSessionEntries: number) {
13
+ super(
14
+ `buffer[${bufferKey}]: live capture refused at the session-entry cap (${maxSessionEntries}); ` +
15
+ "buffered backlog is no longer evicted to admit new sessions. Retry after extraction frees an entry",
16
+ );
17
+ this.name = "BufferAdmissionRefusedError";
18
+ }
19
+ }
20
+
21
+ /** Durable observe capture refused at the entry cap; the request is retryable. */
22
+ export class ReplayAdmissionRefusedError extends Error {
23
+ constructor(message: string) {
24
+ super(message);
25
+ this.name = "ReplayAdmissionRefusedError";
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Live-admission bound (#1908 policy, backlog-safe): a NEW session enters
31
+ * only while under the cap or when an EMPTY entry can be reclaimed. Buffered
32
+ * backlog is never dropped for a live capture; the caller refuses instead.
33
+ */
34
+ export function canAdmitNewEntryUnlocked(
35
+ entries: Readonly<Record<string, BufferEntryState>>,
36
+ bufferKey: string,
37
+ maxSessionEntries: number,
38
+ hooks: BufferEvictionHooks,
39
+ ): boolean {
40
+ const count = Object.keys(entries).length;
41
+ if (count < maxSessionEntries) return true;
42
+ // Probe post-admission (as if this key had landed): admission is safe
43
+ // exactly when an EMPTY entry could then be reclaimed.
44
+ return selectEvictedBufferKeys(entries, [bufferKey], Math.max(0, count - 1), hooks).length > 0;
45
+ }
@@ -0,0 +1,211 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { SmartBuffer, BufferAdmissionRefusedError } from "./buffer.js";
4
+ import { parseConfig } from "./config.js";
5
+ import { log } from "./logger.js";
6
+ import { TurnIngestionCoordinator } from "./orchestration/turn-ingestion.js";
7
+ import type { BufferEntryState, BufferState, BufferTurn } from "./types.js";
8
+
9
+ class FakeStorage {
10
+ public saved: BufferState | null = null;
11
+ public saveCount = 0;
12
+
13
+ constructor(private readonly initial: BufferState) {}
14
+
15
+ async loadBuffer(): Promise<BufferState> {
16
+ return structuredClone(this.initial);
17
+ }
18
+
19
+ async saveBuffer(state: BufferState): Promise<void> {
20
+ this.saveCount += 1;
21
+ this.saved = structuredClone(state);
22
+ }
23
+ }
24
+
25
+ function makeTurn(key: string, content: string, accepted = false): BufferTurn {
26
+ const turn: BufferTurn = {
27
+ role: "user",
28
+ content,
29
+ timestamp: new Date().toISOString(),
30
+ sessionKey: key,
31
+ };
32
+ if (accepted) turn.acceptedCapture = true;
33
+ return turn;
34
+ }
35
+
36
+ function entry(turns: BufferTurn[]): BufferEntryState {
37
+ return { turns, lastExtractionAt: null, extractionCount: 0 };
38
+ }
39
+
40
+ function emptyEntry(): BufferEntryState {
41
+ return { turns: [], lastExtractionAt: null, extractionCount: 0 };
42
+ }
43
+
44
+ /** default + the given entries; default stays empty unless given. */
45
+ function seededState(entries: Record<string, BufferEntryState>): BufferState {
46
+ const defaultEntry = entries.default ?? emptyEntry();
47
+ return {
48
+ turns: defaultEntry.turns,
49
+ lastExtractionAt: defaultEntry.lastExtractionAt,
50
+ extractionCount: defaultEntry.extractionCount,
51
+ entries: { default: defaultEntry, ...entries },
52
+ };
53
+ }
54
+
55
+ function makeBuffer(state: BufferState, maxSessions: number) {
56
+ const config = parseConfig({ bufferSaveDebounceMs: 0, bufferMaxSessions: maxSessions });
57
+ const storage = new FakeStorage(state);
58
+ const buffer = new SmartBuffer(config, storage);
59
+ return { buffer, storage };
60
+ }
61
+
62
+ const CAP = 4;
63
+
64
+ function savedKeys(storage: FakeStorage): string[] {
65
+ return Object.keys(storage.saved?.entries ?? {});
66
+ }
67
+
68
+ test("live capture at a cap full of non-empty backlog is refused, never evicts buffered turns", async () => {
69
+ const { buffer } = makeBuffer(
70
+ seededState({
71
+ a: entry([makeTurn("a", "backlog a")]),
72
+ b: entry([makeTurn("b", "backlog b")]),
73
+ c: entry([makeTurn("c", "backlog c")]),
74
+ }),
75
+ CAP,
76
+ );
77
+
78
+ await assert.rejects(
79
+ buffer.addTurn("new-session", makeTurn("new-session", "live turn")),
80
+ BufferAdmissionRefusedError,
81
+ );
82
+
83
+ assert.deepEqual(buffer.getTurns("a").map((t) => t.content), ["backlog a"], "oldest backlog lost to live admission");
84
+ assert.deepEqual(buffer.getTurns("b").map((t) => t.content), ["backlog b"]);
85
+ assert.deepEqual(buffer.getTurns("c").map((t) => t.content), ["backlog c"]);
86
+ assert.equal(buffer.getTurns("new-session").length, 0, "refused turn was not buffered");
87
+
88
+ // Accepted captures are subject to the same no-backlog-loss limit: refusal
89
+ // happens before acceptance, preserving accepted-capture non-eviction.
90
+ assert.equal(
91
+ await buffer.addTurnBounded("accepted-1", makeTurn("accepted-1", "accepted", true)),
92
+ null,
93
+ "accepted capture is refused rather than evicting native backlog",
94
+ );
95
+ assert.deepEqual(buffer.getTurns("a").map((t) => t.content), ["backlog a"]);
96
+ assert.deepEqual(buffer.getTurns("b").map((t) => t.content), ["backlog b"]);
97
+ assert.deepEqual(buffer.getTurns("c").map((t) => t.content), ["backlog c"]);
98
+ });
99
+
100
+ test("an already-admitted session keeps accepting turns at a full cap", async () => {
101
+ const { buffer, storage } = makeBuffer(
102
+ seededState({
103
+ a: entry([makeTurn("a", "backlog a")]),
104
+ b: entry([makeTurn("b", "backlog b")]),
105
+ c: entry([makeTurn("c", "backlog c")]),
106
+ }),
107
+ CAP,
108
+ );
109
+
110
+ const decision = await buffer.addTurn("b", makeTurn("b", "another b turn"));
111
+ assert.equal(decision, "keep_buffering");
112
+ assert.deepEqual(buffer.getTurns("b").map((t) => t.content), ["backlog b", "another b turn"]);
113
+ assert.deepEqual(buffer.getTurns("a").map((t) => t.content), ["backlog a"], "existing-session admission must not evict peers");
114
+ assert.deepEqual(buffer.getTurns("c").map((t) => t.content), ["backlog c"]);
115
+ assert.equal(savedKeys(storage).length, CAP, "entry count stays at the cap");
116
+ });
117
+
118
+ test("empty entries are still evicted first to admit a new live session", async () => {
119
+ const { buffer, storage } = makeBuffer(
120
+ seededState({
121
+ "empty-oldest": emptyEntry(),
122
+ n1: entry([makeTurn("n1", "native 1")]),
123
+ n2: entry([makeTurn("n2", "native 2")]),
124
+ }),
125
+ CAP,
126
+ );
127
+
128
+ const decision = await buffer.addTurn("new-session", makeTurn("new-session", "live turn"));
129
+ assert.equal(decision, "keep_buffering");
130
+ const keys = savedKeys(storage);
131
+ assert.ok(!keys.includes("empty-oldest"), "oldest empty entry reclaimed");
132
+ assert.ok(keys.includes("new-session"), "new session admitted");
133
+ assert.deepEqual(buffer.getTurns("n1").map((t) => t.content), ["native 1"], "empty eviction must not touch backlog");
134
+ assert.deepEqual(buffer.getTurns("n2").map((t) => t.content), ["native 2"]);
135
+ });
136
+
137
+ test("a retained-only entry is non-evictable backlog: live admission is refused instead", async () => {
138
+ const retainedOnly: BufferEntryState = {
139
+ turns: [],
140
+ retainedTurns: [makeTurn("r", "deferred retry turn")],
141
+ lastExtractionAt: null,
142
+ extractionCount: 0,
143
+ };
144
+ const { buffer } = makeBuffer(
145
+ seededState({
146
+ r: retainedOnly,
147
+ n1: entry([makeTurn("n1", "native 1")]),
148
+ n2: entry([makeTurn("n2", "native 2")]),
149
+ }),
150
+ CAP,
151
+ );
152
+
153
+ await assert.rejects(
154
+ buffer.addTurn("new-session", makeTurn("new-session", "live turn")),
155
+ BufferAdmissionRefusedError,
156
+ );
157
+ assert.equal(
158
+ buffer.getRetainedDeferredTurns("r").length,
159
+ 1,
160
+ "retained retry turns must not be evicted as 'empty'",
161
+ );
162
+ });
163
+ test("concurrent multi-session admissions reserve capacity without partial batches", async () => {
164
+ const { buffer } = makeBuffer(seededState({}), 2);
165
+ const results = await Promise.all([
166
+ buffer.addTurnsBounded([{ bufferKey: "batch-a", turns: [makeTurn("batch-a", "a")] }]),
167
+ buffer.addTurnsBounded([{ bufferKey: "batch-b", turns: [makeTurn("batch-b", "b")] }]),
168
+ ]);
169
+
170
+ assert.equal(results.filter((result) => result !== null).length, 1);
171
+ assert.equal(results.filter((result) => result === null).length, 1);
172
+ assert.equal(buffer.getTurns("batch-a").length + buffer.getTurns("batch-b").length, 1);
173
+ });
174
+
175
+ test("processTurn propagates retryable admission refusal without queueing extraction", async () => {
176
+ const { buffer } = makeBuffer(
177
+ seededState({
178
+ a: entry([makeTurn("a", "backlog a")]),
179
+ b: entry([makeTurn("b", "backlog b")]),
180
+ c: entry([makeTurn("c", "backlog c")]),
181
+ }),
182
+ CAP,
183
+ );
184
+ const config = parseConfig({
185
+ memoryDir: "/tmp/unused-buffer-eviction-backpressure",
186
+ bufferSaveDebounceMs: 0,
187
+ bufferMaxSessions: CAP,
188
+ });
189
+ const queued: BufferTurn[][] = [];
190
+ type CoordinatorDeps = ConstructorParameters<typeof TurnIngestionCoordinator>[0];
191
+ // SAFETY: this fixture exercises only admission + refusal; the remaining
192
+ // coordinator deps are never reached on this path.
193
+ const deps = Object.assign({} as CoordinatorDeps, {
194
+ buffer,
195
+ config,
196
+ queueBufferedExtraction: async (turns: BufferTurn[]) => {
197
+ queued.push(turns);
198
+ },
199
+ });
200
+ const coordinator = new TurnIngestionCoordinator(deps);
201
+
202
+ await assert.rejects(
203
+ coordinator.processTurn("user", "live turn", "new-session"),
204
+ BufferAdmissionRefusedError,
205
+ "caller must receive retryable refusal rather than successful acknowledgment",
206
+ );
207
+
208
+ assert.equal(queued.length, 0, "a refused turn must not queue extraction");
209
+ assert.equal(buffer.getTurns("new-session").length, 0, "a refused turn must not be buffered");
210
+ assert.deepEqual(buffer.getTurns("a").map((x) => x.content), ["backlog a"], "backlog dropped during refused admission");
211
+ });
@@ -10,11 +10,10 @@ export interface BufferEvictionHooks {
10
10
  }
11
11
 
12
12
  /**
13
- * Pick the keys to evict so `entries` returns to `maxCount`: the oldest empty
14
- * sessions first; retained failed extractions (#1908) keep their turns, so if
15
- * empty entries alone cannot reach the cap the oldest NON-empty sessions go
16
- * too rather than growing unboundedly. "default", the retained keys and
17
- * entries holding an accepted capture are never chosen.
13
+ * Pick empty entries to evict so `entries` returns to `maxCount`. Backlog
14
+ * (active or retained turns) is never evicted; admission must refuse when
15
+ * empty entries cannot free a slot. "default", retained keys and entries
16
+ * holding an accepted capture are never chosen.
18
17
  */
19
18
  export function selectEvictedBufferKeys(
20
19
  entries: Readonly<Record<string, BufferEntryState>>,
@@ -35,17 +34,12 @@ export function selectEvictedBufferKeys(
35
34
  const candidates = keys.filter(
36
35
  (key) => key !== "default" && !retainKeys.includes(key) && !hooks.holdsAcceptedCapture(entries[key]),
37
36
  );
37
+ // Backlog = active turns OR retained retry turns (#1908); only an entry
38
+ // holding neither counts as empty.
38
39
  const emptyRemovable = candidates
39
- .filter((key) => (entries[key]?.turns.length ?? 0) === 0)
40
+ .filter((key) => (entries[key]?.turns.length ?? 0) === 0 && (entries[key]?.retainedTurns?.length ?? 0) === 0)
40
41
  .sort(byOldestActivity);
41
42
 
42
43
  const removableCount = Math.max(0, keys.length - maxCount);
43
- const evicted = emptyRemovable.slice(0, removableCount);
44
- if (evicted.length < removableCount) {
45
- const nonEmptyRemovable = candidates
46
- .filter((key) => (entries[key]?.turns.length ?? 0) > 0)
47
- .sort(byOldestActivity);
48
- return evicted.concat(nonEmptyRemovable.slice(0, removableCount - evicted.length));
49
- }
50
- return evicted;
44
+ return emptyRemovable.slice(0, removableCount);
51
45
  }
@@ -22,7 +22,7 @@ import { mkdtemp, rm } from "node:fs/promises";
22
22
  import os from "node:os";
23
23
  import path from "node:path";
24
24
 
25
- import { SmartBuffer } from "./buffer.js";
25
+ import { SmartBuffer, BufferAdmissionRefusedError } from "./buffer.js";
26
26
  import { parseConfig } from "./config.js";
27
27
  import { StorageManager } from "./storage.js";
28
28
  import type { BufferEntryState, BufferState, BufferTurn } from "./types.js";
@@ -161,34 +161,28 @@ test("clearAfterExtraction persists the cleared session state", async () => {
161
161
  assert.equal(record?.entry.extractionCount, 1);
162
162
  });
163
163
 
164
- test("evicted sessions get their files deleted", async () => {
164
+ test("empty evicted sessions get their files deleted", async () => {
165
165
  const storage = new IncrementalStorage(emptyState());
166
- const buffer = new SmartBuffer(parseConfig({ triggerMode: "every_n", bufferMaxTurns: 100, bufferMaxSessions: 200 }), storage);
167
-
168
- // normalizeState always materializes a "default" entry, so 198 seeded
169
- // sessions + default = 199; new-session reaches exactly 200 (the
170
- // addTurnBounded cap). One more UNBOUNDED addTurn pushes to 201 and
171
- // pruneEntries evicts the oldest non-default session (s0).
172
- for (let i = 0; i < 198; i++) {
173
- const key = `s${i}`;
174
- await buffer.addTurnBounded(key, makeTurn(key, `content ${i}`));
175
- }
166
+ const buffer = new SmartBuffer(parseConfig({ triggerMode: "every_n", bufferMaxTurns: 100, bufferMaxSessions: 4 }), storage);
167
+
168
+ // Clear one persisted entry to make it genuinely empty. Accepted-capture
169
+ // admission may reclaim this slot, but never a session with buffered turns.
170
+ await buffer.addTurn("empty-old", makeTurn("empty-old", "will be extracted"));
171
+ await buffer.flushPendingSave();
172
+ await buffer.clearAfterExtraction("empty-old");
173
+ await buffer.addTurnBounded("native-1", makeTurn("native-1", "native 1"));
174
+ await buffer.addTurnBounded("native-2", makeTurn("native-2", "native 2"));
176
175
  await buffer.flushPendingSave();
177
176
 
178
- const outcome = await buffer.addTurnBounded("new-session", makeTurn("new-session", "fresh"));
179
- assert.notEqual(outcome, null, "addTurnBounded refused below capacity");
180
- await buffer.addTurn("overflow", makeTurn("overflow", "one too many"));
181
- // The evicted key's file deletion lands on the next persist, like any
182
- // other buffered state change.
177
+ const accepted = await buffer.addTurnBounded("accepted", {
178
+ ...makeTurn("accepted", "durable accepted"),
179
+ acceptedCapture: true,
180
+ });
181
+ assert.notEqual(accepted, null, "empty entry should be reclaimed to admit accepted capture");
183
182
  await buffer.flushPendingSave();
184
- assert.ok(storage.deleted.includes("s0"), "oldest session was not evicted under capacity pressure");
185
- assert.ok(
186
- storage.deleted.length > 0,
187
- "capacity pressure evicted entries without deleting their session files",
188
- );
189
- for (const key of storage.deleted) {
190
- assert.equal(storage.sessions.has(key), false, `evicted session ${key} still has a file record`);
191
- }
183
+ assert.ok(storage.deleted.includes("empty-old"), "reclaimed empty entry's file was not deleted");
184
+ assert.equal(buffer.getTurns("native-1").length, 1);
185
+ assert.equal(buffer.getTurns("native-2").length, 1);
192
186
  });
193
187
 
194
188
  test("the default entry cap leaves headroom for live sessions during backlog drains (#3303)", async () => {
@@ -273,9 +267,7 @@ test("legacy-only storage keeps the whole-state saveBuffer behavior", async () =
273
267
  assert.equal(storage.lastSaved?.entries?.["thread-b"]?.turns[0]?.content, "beta");
274
268
  });
275
269
 
276
- test("durable capture at the entry cap evicts native entries instead of refusing (COS-244c)", async () => {
277
- // Stalled extraction left the cap full of native (non-accepted)
278
- // sessions; addTurnBounded refused every new accepted session.
270
+ test("durable capture at the entry cap is refused without evicting native backlog (COS-244c)", async () => {
279
271
  const storage = new IncrementalStorage(emptyState());
280
272
  const buffer = new SmartBuffer(
281
273
  parseConfig({ triggerMode: "every_n", bufferMaxTurns: 100, bufferMaxSessions: 4 }),
@@ -286,17 +278,13 @@ test("durable capture at the entry cap evicts native entries instead of refusing
286
278
  await buffer.flushPendingSave();
287
279
 
288
280
  const accepted = (key: string): BufferTurn => ({ ...makeTurn(key, "accepted"), acceptedCapture: true });
281
+ assert.equal(await buffer.addTurnBounded("accepted-0", accepted("accepted-0")), null);
282
+ await buffer.flushPendingSave();
283
+ assert.equal(storage.deleted.length, 0, "accepted refusal must not evict native session files");
289
284
  for (let i = 0; i < 3; i++) {
290
- const outcome = await buffer.addTurnBounded(`accepted-${i}`, accepted(`accepted-${i}`));
291
- assert.notEqual(outcome, null, `accepted-${i} refused while native entries were evictable`);
285
+ assert.ok(buffer.getTurns(`native-${i}`).some((turn) => turn.content === `native ${i}`));
292
286
  }
293
- await buffer.flushPendingSave();
294
- assert.ok(storage.deleted.includes("native-0"), "oldest native entry was not evicted");
295
-
296
- // Accepted work is still never evicted: with only accepted entries left the
297
- // cap refuses rather than dropping accepted captures.
298
- assert.equal(await buffer.addTurnBounded("accepted-3", accepted("accepted-3")), null);
299
- for (let i = 0; i < 3; i++) assert.ok(!storage.deleted.includes(`accepted-${i}`));
287
+ assert.equal(buffer.getTurns("accepted-0").length, 0, "refused capture was never accepted");
300
288
  });
301
289
 
302
290
  test("at-cap admission evicts nothing when eviction cannot free a slot (COS-244c)", async () => {
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { SmartBuffer } from "./buffer.js";
3
+ import { SmartBuffer, BufferAdmissionRefusedError } from "./buffer.js";
4
4
  import { parseConfig } from "./config.js";
5
5
  import type { BufferSurpriseProbe } from "./buffer.js";
6
6
  import type { BufferState, BufferTurn } from "./types.js";
@@ -642,13 +642,14 @@ test("getTurns prepends retained deferred turns before live turns", async () =>
642
642
  assert.equal(all[1]?.content, "new live turn");
643
643
  });
644
644
 
645
- test("SmartBuffer prefers pruning empty entries and bounds pending-turn buffers at the cap (#1908 policy)", async () => {
645
+ test("SmartBuffer refuses new sessions instead of dropping backlog at the cap (#1908 policy)", async () => {
646
646
  // Pre-#1908 this contract was "never prune non-empty entries". With retry
647
647
  // retention (failed extractions keep their turns), unbounded non-empty
648
648
  // growth during a provider outage would eventually OOM the daemon — the
649
- // documented policy (#1908 step 7) is: retain up to MAX_BUFFER_ENTRY_COUNT
650
- // session entries, then prune the OLDEST sessions with a loud warning.
651
- // Empty entries are still evicted first.
649
+ // documented policy (#1908 step 7) bounds the ENTRY COUNT, not by dropping
650
+ // buffered turns: a NEW session is refused (retryable backpressure) when
651
+ // only non-empty backlog fills the cap. Empty entries are still evicted
652
+ // first, and already-admitted sessions keep buffering.
652
653
  const entries = Object.fromEntries(
653
654
  Array.from({ length: 205 }, (_, index) => [
654
655
  `thread-${index}`,
@@ -674,21 +675,32 @@ test("SmartBuffer prefers pruning empty entries and bounds pending-turn buffers
674
675
  });
675
676
  const buffer = new SmartBuffer(parseConfig({ bufferSaveDebounceMs: 0, bufferMaxSessions: 200 }), storage as unknown as ConstructorParameters<typeof SmartBuffer>[1]);
676
677
 
677
- await buffer.addTurn("active-thread", makeTurn("active-thread", "pending memory"));
678
+ await assert.rejects(
679
+ buffer.addTurn("active-thread", makeTurn("active-thread", "pending memory")),
680
+ BufferAdmissionRefusedError,
681
+ "a new session must be refused while only non-empty backlog fills the cap",
682
+ );
683
+
684
+ // Already-admitted sessions keep buffering; the save reflects the
685
+ // post-refusal state: nothing was evicted, nothing refused was buffered.
686
+ await buffer.addTurn("thread-204", makeTurn("thread-204", "another turn"));
687
+ assert.equal(buffer.getTurns("thread-204").length, 2);
678
688
 
679
689
  const persistedKeys = Object.keys(storage.saved?.entries ?? {});
680
- assert.ok(persistedKeys.length <= 201, `bounded at the cap plus default (got ${persistedKeys.length})`);
690
+ assert.ok(persistedKeys.length <= 206, `entry count stays at the seeded cap state (got ${persistedKeys.length})`);
681
691
  assert.ok(persistedKeys.includes("default"), "default entry never pruned");
682
- assert.ok(persistedKeys.includes("active-thread"), "the just-written key is retained");
683
- assert.ok(!persistedKeys.includes("thread-0"), "oldest pending entry pruned once over the cap");
692
+ assert.ok(persistedKeys.includes("thread-0"), "oldest backlog keeps its turns for extraction");
684
693
  assert.ok(persistedKeys.includes("thread-204"), "newest pending entry survives");
694
+ assert.ok(!persistedKeys.includes("active-thread"), "the refused turn was never buffered");
685
695
  });
686
696
 
687
- test("pruneEntries evicts oldest NON-empty session entries past the cap (#1908 retry-retention bound)", async () => {
697
+ test("pruneEntries never evicts non-empty backlog; live admission is refused past the cap (#1908 retry-retention bound)", async () => {
688
698
  // Retained failed extractions keep their turns in the buffer, so during a
689
699
  // provider outage non-empty entries can exceed MAX_BUFFER_ENTRY_COUNT (200).
690
- // The cap is the documented bound: the oldest non-empty sessions must be
691
- // evicted (loudly) rather than growing without bound (codex review).
700
+ // The cap is still the documented bound: buffered turn data is never the
701
+ // release valve — a NEW session is refused (retryable backpressure) until
702
+ // extraction drains an entry, so the daemon cannot OOM on unbounded growth
703
+ // and no buffered turn is dropped (codex review).
692
704
  const entries: NonNullable<BufferState["entries"]> = {};
693
705
  for (let i = 0; i < 210; i += 1) {
694
706
  const at = new Date(1_700_000_000_000 + i * 60_000).toISOString();
@@ -701,13 +713,17 @@ test("pruneEntries evicts oldest NON-empty session entries past the cap (#1908 r
701
713
  const storage = new FakeStorage({ turns: [], lastExtractionAt: null, extractionCount: 0, entries });
702
714
  const buffer = new SmartBuffer(parseConfig({ bufferSaveDebounceMs: 0, bufferMaxSessions: 200 }), storage as unknown as ConstructorParameters<typeof SmartBuffer>[1]);
703
715
  await buffer.load();
704
- await buffer.addTurn("session-newest", makeTurn("session-newest", "new turn"));
716
+ await assert.rejects(
717
+ buffer.addTurn("session-newest", makeTurn("session-newest", "new turn")),
718
+ BufferAdmissionRefusedError,
719
+ "live admission refuses rather than evicting oldest non-empty backlog",
720
+ );
705
721
 
706
722
  const savedKeys = Object.keys(storage.saved?.entries ?? {});
707
- assert.ok(savedKeys.length <= 200, `entries bounded at MAX_BUFFER_ENTRY_COUNT (got ${savedKeys.length})`);
708
- assert.ok(savedKeys.includes("session-newest"), "the just-written key is retained");
709
- assert.equal(buffer.getTurns("session-000").length, 0, "oldest non-empty entry was evicted");
723
+ assert.ok(savedKeys.length <= 211, `entries bounded at MAX_BUFFER_ENTRY_COUNT plus default (got ${savedKeys.length})`);
724
+ assert.equal(buffer.getTurns("session-000").length, 1, "oldest non-empty entry keeps its turn for extraction");
710
725
  assert.ok(buffer.getTurns("session-209").length > 0, "newest pre-existing entry survives");
726
+ assert.ok(!savedKeys.includes("session-newest"), "the refused turn was never buffered");
711
727
  });
712
728
 
713
729
  // ---------------------------------------------------------------------------
@@ -398,3 +398,44 @@ export function probeWithTimeout<T>(inflight: Promise<T>, timeoutMs: number): Pr
398
398
  if (timer) clearTimeout(timer);
399
399
  });
400
400
  }
401
+
402
+ export const isAcceptedTurn = (t: { acceptedCapture?: boolean; writeNamespaceOverride?: string }): boolean =>
403
+ t.acceptedCapture === true || t.writeNamespaceOverride !== undefined;
404
+
405
+ export function entryActivityAt(entry: BufferEntryState): number {
406
+ const lastTurnAt = entry.turns.reduce((latest, turn) => {
407
+ const parsed = Date.parse(turn.timestamp);
408
+ return Number.isFinite(parsed) ? Math.max(latest, parsed) : latest;
409
+ }, -1);
410
+ const lastExtractionAt =
411
+ entry.lastExtractionAt === null ? Number.NaN : Date.parse(entry.lastExtractionAt);
412
+ return Math.max(
413
+ lastTurnAt,
414
+ Number.isFinite(lastExtractionAt) ? lastExtractionAt : -1,
415
+ );
416
+ }
417
+
418
+ export function entryHoldsAcceptedCapture(entry: BufferEntryState | undefined): boolean {
419
+ return (entry?.turns ?? []).some(isAcceptedTurn) || (entry?.retainedTurns ?? []).some(isAcceptedTurn);
420
+ }
421
+
422
+ export function normalizeBufferState(state: BufferState): BufferState {
423
+ // SAFETY: persisted-state contract — entries, when present, is a record of BufferEntryState.
424
+ const entries = Object.assign(
425
+ Object.create(null),
426
+ state.entries ?? {},
427
+ ) as NonNullable<BufferState["entries"]>;
428
+ if (!entries.default) {
429
+ entries.default = {
430
+ turns: Array.isArray(state.turns) ? [...state.turns] : [],
431
+ lastExtractionAt: state.lastExtractionAt ?? null,
432
+ extractionCount: state.extractionCount ?? 0,
433
+ };
434
+ }
435
+ return {
436
+ turns: entries.default.turns,
437
+ lastExtractionAt: entries.default.lastExtractionAt,
438
+ extractionCount: entries.default.extractionCount,
439
+ entries,
440
+ };
441
+ }