@remnic/core 10.0.84 → 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 (545) hide show
  1. package/dist/access-admin-ops-surface.js +90 -88
  2. package/dist/access-authorization-probe.js +91 -89
  3. package/dist/access-boundary.d.ts.map +1 -1
  4. package/dist/access-boundary.js +90 -88
  5. package/dist/access-cli.js +198 -194
  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 +90 -88
  11. package/dist/access-http-briefing.js +91 -89
  12. package/dist/access-http-query.js +92 -90
  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 +119 -116
  16. package/dist/access-identity-continuity-surface.js +90 -88
  17. package/dist/access-lcm-surface.js +90 -88
  18. package/dist/access-mcp-frames.d.ts +29 -0
  19. package/dist/access-mcp-frames.d.ts.map +1 -0
  20. package/dist/access-mcp-frames.js +10 -0
  21. package/dist/access-mcp.d.ts +1 -1
  22. package/dist/access-mcp.d.ts.map +1 -1
  23. package/dist/access-mcp.js +105 -102
  24. package/dist/access-memory-search-fanout.js +2 -2
  25. package/dist/access-namespace-preflight.js +90 -88
  26. package/dist/access-observe-write-category.js +15 -14
  27. package/dist/access-observe-write-surface.d.ts.map +1 -1
  28. package/dist/access-observe-write-surface.js +90 -88
  29. package/dist/access-offline-manifest.js +10 -10
  30. package/dist/access-operations-batch.js +94 -92
  31. package/dist/access-operations.js +101 -99
  32. package/dist/access-recall-concurrency.js +90 -88
  33. package/dist/access-recall-diagnostics.js +2 -2
  34. package/dist/access-recall-response.js +90 -88
  35. package/dist/access-recall-surface.js +90 -88
  36. package/dist/access-schema.d.ts.map +1 -1
  37. package/dist/access-schema.js +10 -9
  38. package/dist/access-service.d.ts +3 -3
  39. package/dist/access-service.d.ts.map +1 -1
  40. package/dist/access-service.js +90 -88
  41. package/dist/access-surface-catalog.js +92 -90
  42. package/dist/access-surface-catalog.js.map +1 -1
  43. package/dist/access-token-capabilities.js +2 -2
  44. package/dist/active-recall.js +38 -38
  45. package/dist/adapters/index.js +7 -7
  46. package/dist/adapters/registry.js +3 -3
  47. package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
  48. package/dist/briefing-deadline.d.ts +2 -2
  49. package/dist/briefing-deadline.d.ts.map +1 -1
  50. package/dist/briefing-deadline.js +1 -1
  51. package/dist/briefing.js +25 -25
  52. package/dist/buffer-admission.d.ts +22 -0
  53. package/dist/buffer-admission.d.ts.map +1 -0
  54. package/dist/buffer-admission.js +13 -0
  55. package/dist/buffer-eviction.d.ts +4 -5
  56. package/dist/buffer-eviction.d.ts.map +1 -1
  57. package/dist/buffer-eviction.js +1 -1
  58. package/dist/buffer-persistence.d.ts +8 -4
  59. package/dist/buffer-persistence.d.ts.map +1 -1
  60. package/dist/buffer-persistence.js +1 -1
  61. package/dist/buffer-turn-helpers.d.ts +12 -0
  62. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  63. package/dist/buffer-turn-helpers.js +9 -1
  64. package/dist/buffer-types.d.ts +24 -1
  65. package/dist/buffer-types.d.ts.map +1 -1
  66. package/dist/buffer.d.ts +21 -19
  67. package/dist/buffer.d.ts.map +1 -1
  68. package/dist/buffer.js +17 -13
  69. package/dist/calibration.js +3 -3
  70. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
  71. package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
  72. package/dist/capsule-gunzip-KGBDGF7P.js.map +1 -0
  73. package/dist/capsule-import-gate.d.ts +11 -0
  74. package/dist/capsule-import-gate.d.ts.map +1 -0
  75. package/dist/capsule-import-gate.js +12 -0
  76. package/dist/capsule-import-gate.js.map +1 -0
  77. package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
  78. package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
  79. package/dist/causal-behavior.js +2 -2
  80. package/dist/causal-chain.js +2 -2
  81. package/dist/causal-consolidation.js +27 -27
  82. package/dist/causal-retrieval.js +2 -2
  83. package/dist/causal-trajectory-graph.js +2 -2
  84. package/dist/causal-trajectory.js +1 -1
  85. package/dist/{chunk-MSIRVXQ3.js → chunk-2G4TV2ML.js} +12 -12
  86. package/dist/{chunk-B74NPUHT.js → chunk-2JG2NI7C.js} +54 -54
  87. package/dist/chunk-2JG2NI7C.js.map +1 -0
  88. package/dist/{chunk-IR4C3DP2.js → chunk-2K3AUDH7.js} +2 -2
  89. package/dist/{chunk-2IW5PT7U.js → chunk-2N4OUSIA.js} +2 -2
  90. package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
  91. package/dist/{chunk-QZSIDSB2.js → chunk-2NUDHVYM.js} +2 -2
  92. package/dist/{chunk-HPFVZPRH.js → chunk-2U4LMSEX.js} +3 -3
  93. package/dist/{chunk-WHQXWFH7.js → chunk-2XOFVTTT.js} +2 -2
  94. package/dist/{chunk-I6ORDT54.js → chunk-32CA7B6L.js} +2 -2
  95. package/dist/{chunk-T77AX6DG.js → chunk-32YX7EJ3.js} +4 -4
  96. package/dist/{chunk-HLKUZW42.js → chunk-3CTKEXXF.js} +144 -156
  97. package/dist/chunk-3CTKEXXF.js.map +1 -0
  98. package/dist/{chunk-HLWXCDUQ.js → chunk-3DAWHP5K.js} +5 -5
  99. package/dist/{chunk-ZJJFUEPO.js → chunk-3PJEDHLD.js} +2 -2
  100. package/dist/{chunk-PJYK7HH2.js → chunk-3UZ2VMKD.js} +2 -2
  101. package/dist/{chunk-IN4OFAHX.js → chunk-3Y5X6YFH.js} +2 -2
  102. package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
  103. package/dist/{chunk-T6T2RMWW.js → chunk-4GMI5HFH.js} +12 -12
  104. package/dist/{chunk-YOEYL6CH.js → chunk-4LCVA2LW.js} +3 -3
  105. package/dist/{chunk-66JOLMWS.js → chunk-4X7HQKAG.js} +2 -2
  106. package/dist/{chunk-65SKM4BF.js → chunk-4XYAWK5H.js} +2 -2
  107. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  108. package/dist/{chunk-TPX7XXY4.js → chunk-5SOSQTVE.js} +2 -2
  109. package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
  110. package/dist/{chunk-BK7AJUWB.js → chunk-6BCWFCDG.js} +7 -7
  111. package/dist/{chunk-V27FK6D6.js → chunk-6XTUZKCZ.js} +2 -2
  112. package/dist/{chunk-KASJJGRG.js → chunk-6ZELOK6O.js} +3 -8
  113. package/dist/chunk-6ZELOK6O.js.map +1 -0
  114. package/dist/{chunk-LECSS7EE.js → chunk-73UZZFJC.js} +3 -3
  115. package/dist/{chunk-4EACCIPO.js → chunk-77PDJ4OE.js} +3 -3
  116. package/dist/{chunk-GQZSGXR6.js → chunk-7GSL4LBG.js} +2 -2
  117. package/dist/{chunk-B6SPPYGJ.js → chunk-7IWRLYFJ.js} +2 -2
  118. package/dist/{chunk-R45L4TV6.js → chunk-7LCGFUMB.js} +6 -6
  119. package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
  120. package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
  121. package/dist/{chunk-ZXF3O7P4.js → chunk-ARRTLPRC.js} +2 -2
  122. package/dist/{chunk-S5CQI2KF.js → chunk-B3GKQ6DU.js} +2 -2
  123. package/dist/{chunk-XRBHIR7Q.js → chunk-B56LORLY.js} +2 -2
  124. package/dist/{chunk-KO3YBHOV.js → chunk-BIVKPQ6Q.js} +3 -3
  125. package/dist/{chunk-QZBTIYKD.js → chunk-BLSHFIR6.js} +7 -6
  126. package/dist/chunk-BLSHFIR6.js.map +1 -0
  127. package/dist/{chunk-54FDG7K7.js → chunk-C2HORX76.js} +3 -3
  128. package/dist/{chunk-WAD32CZG.js → chunk-CC4V7HNP.js} +43 -30
  129. package/dist/chunk-CC4V7HNP.js.map +1 -0
  130. package/dist/{chunk-LGEH473S.js → chunk-CJCTKL6Y.js} +9 -1
  131. package/dist/chunk-CJCTKL6Y.js.map +1 -0
  132. package/dist/{chunk-E33UJHY3.js → chunk-CN7Q3T2J.js} +4 -4
  133. package/dist/{chunk-YUTG35M4.js → chunk-CYCEROPR.js} +3 -3
  134. package/dist/{chunk-TFLPDNQV.js → chunk-D5EV6Y2L.js} +3 -3
  135. package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
  136. package/dist/{chunk-B4ZU34JQ.js → chunk-DT7SNJH6.js} +3 -3
  137. package/dist/{chunk-VLMBSGQF.js → chunk-DZ7DPDVW.js} +10 -8
  138. package/dist/chunk-DZ7DPDVW.js.map +1 -0
  139. package/dist/{chunk-FVRTCYZ3.js → chunk-EA2LA5RI.js} +2 -2
  140. package/dist/{chunk-WBN66HPI.js → chunk-ELHJK7LM.js} +6 -6
  141. package/dist/{chunk-77R7BQZ2.js → chunk-EQ4U73UH.js} +77 -24
  142. package/dist/chunk-EQ4U73UH.js.map +1 -0
  143. package/dist/{chunk-3JPTUCD7.js → chunk-EUBDYOLD.js} +32 -23
  144. package/dist/chunk-EUBDYOLD.js.map +1 -0
  145. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  146. package/dist/{chunk-YAUHSNPB.js → chunk-F74G4KIH.js} +2 -2
  147. package/dist/{chunk-MPGLM2VU.js → chunk-FF3EFLUT.js} +7 -5
  148. package/dist/chunk-FF3EFLUT.js.map +1 -0
  149. package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
  150. package/dist/chunk-FKBGK3NC.js +62 -0
  151. package/dist/chunk-FKBGK3NC.js.map +1 -0
  152. package/dist/{chunk-X2KO4FK5.js → chunk-FNFURZBZ.js} +5 -5
  153. package/dist/chunk-FNFURZBZ.js.map +1 -0
  154. package/dist/{chunk-KESJ5XX3.js → chunk-G54BOKZ6.js} +2 -2
  155. package/dist/{chunk-YK7GOPIE.js → chunk-G67EUQAB.js} +9 -7
  156. package/dist/chunk-G67EUQAB.js.map +1 -0
  157. package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
  158. package/dist/{chunk-OQSSQOKQ.js → chunk-GULUWOA5.js} +2 -2
  159. package/dist/{chunk-K6RTQZA4.js → chunk-HFUKXOUW.js} +2 -2
  160. package/dist/{chunk-WV254W4V.js → chunk-HLN2HOXX.js} +2 -2
  161. package/dist/{chunk-FUOAIJKQ.js → chunk-IBDWYRDR.js} +3 -3
  162. package/dist/{chunk-Z3DDQXM4.js → chunk-IBGU6AN6.js} +45 -45
  163. package/dist/chunk-IBGU6AN6.js.map +1 -0
  164. package/dist/{chunk-AJG2TAMR.js → chunk-IEEV27BC.js} +2 -2
  165. package/dist/chunk-IGLJNHOX.js +62 -0
  166. package/dist/chunk-IGLJNHOX.js.map +1 -0
  167. package/dist/{chunk-LTDLJCMQ.js → chunk-IJ3OEECW.js} +4 -4
  168. package/dist/{chunk-BK4FBHZ3.js → chunk-IPD5GC7C.js} +3 -3
  169. package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
  170. package/dist/{chunk-GCAQ3VUJ.js → chunk-JM2YZKCQ.js} +2 -2
  171. package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
  172. package/dist/{chunk-LU66I56Q.js → chunk-KVSOEVAF.js} +61 -12
  173. package/dist/chunk-KVSOEVAF.js.map +1 -0
  174. package/dist/chunk-M3BEJJNV.js +31 -0
  175. package/dist/chunk-M3BEJJNV.js.map +1 -0
  176. package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
  177. package/dist/{chunk-XHGYZRVW.js → chunk-NIVKG3DS.js} +5 -5
  178. package/dist/{chunk-VZELUMKK.js → chunk-NQP4ZYGG.js} +2 -2
  179. package/dist/{chunk-7PMOCQOL.js → chunk-NT7F7WJQ.js} +192 -63
  180. package/dist/chunk-NT7F7WJQ.js.map +1 -0
  181. package/dist/{chunk-YO2JEX2Z.js → chunk-OGBQMY46.js} +2 -2
  182. package/dist/{chunk-TLHF75OR.js → chunk-OL553AWT.js} +12 -12
  183. package/dist/{chunk-CLVILSBR.js → chunk-OZNBZJ3S.js} +22 -22
  184. package/dist/{chunk-VUROWXFL.js → chunk-P452SEKO.js} +2 -2
  185. package/dist/{chunk-JX3E4XCW.js → chunk-PDOOUXCS.js} +2 -2
  186. package/dist/{chunk-6QYPL6NI.js → chunk-PKZ274PI.js} +3 -3
  187. package/dist/{chunk-KJCL2FZO.js → chunk-QD5R6U5J.js} +2 -2
  188. package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
  189. package/dist/{chunk-VKDAJJQP.js → chunk-QEZ3DW5O.js} +11 -11
  190. package/dist/{chunk-WYPCJK6K.js → chunk-QKERFEJS.js} +2 -2
  191. package/dist/{chunk-BDN3TDGV.js → chunk-QKES7GU6.js} +2 -2
  192. package/dist/{chunk-PCQWKMOL.js → chunk-QNSZYTSG.js} +2 -2
  193. package/dist/chunk-QV2VZKD5.js +31 -0
  194. package/dist/chunk-QV2VZKD5.js.map +1 -0
  195. package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
  196. package/dist/{chunk-CIIHIAJ2.js → chunk-ROGO7FRA.js} +6 -5
  197. package/dist/{chunk-CIIHIAJ2.js.map → chunk-ROGO7FRA.js.map} +1 -1
  198. package/dist/{chunk-NUSAAN75.js → chunk-ROKDHO74.js} +2 -2
  199. package/dist/{chunk-D5SSBCYY.js → chunk-RWGRSKN5.js} +7 -5
  200. package/dist/chunk-RWGRSKN5.js.map +1 -0
  201. package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
  202. package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
  203. package/dist/{chunk-XT45WCKX.js → chunk-SYKJBS7S.js} +10 -10
  204. package/dist/{chunk-5BE3FLKL.js → chunk-TAUHAP5J.js} +4 -4
  205. package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
  206. package/dist/{chunk-XYP5WBDO.js → chunk-TQ4SCPGQ.js} +76 -60
  207. package/dist/chunk-TQ4SCPGQ.js.map +1 -0
  208. package/dist/{chunk-K3B72DKJ.js → chunk-TRMZHGIX.js} +8 -8
  209. package/dist/{chunk-WBDIZWJQ.js → chunk-TSS3D7ZY.js} +2 -2
  210. package/dist/{chunk-6LCWKS62.js → chunk-UAUV3BWS.js} +104 -121
  211. package/dist/chunk-UAUV3BWS.js.map +1 -0
  212. package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
  213. package/dist/chunk-UDKWFDV2.js.map +1 -0
  214. package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
  215. package/dist/{chunk-GGJFRVRD.js → chunk-US7KPQA5.js} +2 -2
  216. package/dist/{chunk-FJ2VJTGA.js → chunk-UWRHHRDK.js} +3 -3
  217. package/dist/{chunk-4KLQI3L2.js → chunk-UXXK523X.js} +40 -2
  218. package/dist/chunk-UXXK523X.js.map +1 -0
  219. package/dist/{chunk-SDESKFKT.js → chunk-UYHDMCY2.js} +2 -2
  220. package/dist/{chunk-3T3VHXUH.js → chunk-VKILERSL.js} +2 -2
  221. package/dist/{chunk-2TGIKMMZ.js → chunk-WXDTOE6F.js} +2 -2
  222. package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
  223. package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
  224. package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
  225. package/dist/chunk-XO23OKMK.js.map +1 -0
  226. package/dist/{chunk-WC475JOG.js → chunk-XU37EJTW.js} +24 -24
  227. package/dist/chunk-XU37EJTW.js.map +1 -0
  228. package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
  229. package/dist/{chunk-QA3Y2O6H.js → chunk-YAVIBVCU.js} +2 -2
  230. package/dist/{chunk-2XJZLSXE.js → chunk-YL4UKMD7.js} +124 -58
  231. package/dist/chunk-YL4UKMD7.js.map +1 -0
  232. package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
  233. package/dist/{chunk-HWHNQVRV.js → chunk-YMJRLT7S.js} +28 -8
  234. package/dist/chunk-YMJRLT7S.js.map +1 -0
  235. package/dist/{chunk-EBOHFZI4.js → chunk-YRZPBN46.js} +3 -3
  236. package/dist/{chunk-FMFCQRDM.js → chunk-ZANNIXXE.js} +3 -3
  237. package/dist/{chunk-HQGATKG2.js → chunk-ZBFLV657.js} +3 -3
  238. package/dist/{chunk-6L63IN3M.js → chunk-ZOJUWVAU.js} +1 -1
  239. package/dist/cli.js +144 -141
  240. package/dist/coding/export-okf-codegraph.js +24 -24
  241. package/dist/coding/pre-action-gate.js +2 -2
  242. package/dist/compounding/engine.js +26 -26
  243. package/dist/config.js +35 -35
  244. package/dist/connectors/codex-materialize-runner.js +24 -24
  245. package/dist/connectors/index.js +28 -28
  246. package/dist/contradiction/index.js +4 -4
  247. package/dist/convergence-refresh.js +26 -26
  248. package/dist/corpus-watermark.js +28 -28
  249. package/dist/dashboard-runtime.js +2 -2
  250. package/dist/deep-recall-config.js +2 -2
  251. package/dist/deep-recall-http-glue.js +12 -12
  252. package/dist/entity-retrieval.js +25 -25
  253. package/dist/event-order-recall.js +2 -2
  254. package/dist/explicit-capture.js +6 -6
  255. package/dist/external-wiki-access.js +91 -89
  256. package/dist/extraction-evidence-class.js +2 -2
  257. package/dist/extraction-quote-grounding.d.ts +5 -1
  258. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  259. package/dist/extraction-quote-grounding.js +1 -1
  260. package/dist/extraction-source-grounding-rules.d.ts +3 -1
  261. package/dist/extraction-source-grounding-rules.d.ts.map +1 -1
  262. package/dist/extraction-source-grounding-rules.js +2 -2
  263. package/dist/extraction-source-grounding.d.ts +1 -1
  264. package/dist/extraction-source-grounding.d.ts.map +1 -1
  265. package/dist/extraction-source-grounding.js +5 -5
  266. package/dist/extraction-span-materialize.js +2 -2
  267. package/dist/extraction.d.ts.map +1 -1
  268. package/dist/extraction.js +43 -43
  269. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  270. package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
  271. package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
  272. package/dist/graph-jsonl.js +3 -3
  273. package/dist/graph-snapshot.js +3 -3
  274. package/dist/graph.js +2 -2
  275. package/dist/harmonic-retrieval.js +24 -24
  276. package/dist/index.js +255 -251
  277. package/dist/lcm/engine.js +2 -2
  278. package/dist/lcm/index.js +5 -5
  279. package/dist/local-llm.js +3 -3
  280. package/dist/location/index.js +28 -28
  281. package/dist/maintenance/memory-governance.js +24 -24
  282. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +24 -24
  283. package/dist/maintenance/rebuild-memory-projection-runner.js +26 -26
  284. package/dist/maintenance/rebuild-memory-projection.js +25 -25
  285. package/dist/memory-browse-http-glue.js +2 -2
  286. package/dist/memory-browse-operations.js +91 -89
  287. package/dist/memory-browse.js +5 -5
  288. package/dist/memory-subject.js +2 -2
  289. package/dist/namespaces/migrate.js +25 -25
  290. package/dist/namespaces/search.d.ts +9 -0
  291. package/dist/namespaces/search.d.ts.map +1 -1
  292. package/dist/namespaces/search.js +13 -7
  293. package/dist/namespaces/storage.js +24 -24
  294. package/dist/offline-sync-changeset-generations.js +3 -3
  295. package/dist/offline-sync-embedding-generation.js +2 -2
  296. package/dist/offline-sync-impression-drain.js +2 -2
  297. package/dist/offline-sync-snapshot-stream.js +6 -6
  298. package/dist/offline-sync.js +5 -5
  299. package/dist/operator-doctor-corpus.js +31 -31
  300. package/dist/operator-doctor-replica.js +4 -4
  301. package/dist/operator-toolkit.js +49 -49
  302. package/dist/orchestration/backlog-scheduler.d.ts +3 -2
  303. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  304. package/dist/orchestration/backlog-scheduler.js +209 -205
  305. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  306. package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
  307. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  308. package/dist/orchestration/buffer-drain-runner.js +199 -195
  309. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  310. package/dist/orchestration/fact-dedup-window.d.ts +1 -4
  311. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  312. package/dist/orchestration/maintenance.js +41 -41
  313. package/dist/orchestration/turn-ingestion.d.ts +2 -9
  314. package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
  315. package/dist/orchestrator.js +198 -194
  316. package/dist/provenance.js +2 -2
  317. package/dist/qmd.js +4 -4
  318. package/dist/recall-navigation-http-glue.js +2 -2
  319. package/dist/recall-navigation-operations.js +91 -89
  320. package/dist/recall-navigation.js +90 -88
  321. package/dist/recall-state-view-admission.js +5 -5
  322. package/dist/recall-why-service.js +5 -5
  323. package/dist/reconcile/manifest.js +2 -2
  324. package/dist/replica-divergence.js +3 -3
  325. package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
  326. package/dist/resolution-23ZBDM6A.js.map +1 -0
  327. package/dist/resume-bundles.js +36 -36
  328. package/dist/salvage-envelope.js +4 -4
  329. package/dist/search/factory.js +6 -6
  330. package/dist/search/index.js +12 -12
  331. package/dist/semantic-consolidation.js +25 -25
  332. package/dist/semantic-rule-promotion.js +24 -24
  333. package/dist/semantic-rule-verifier.js +24 -24
  334. package/dist/source-agent-qualifier.d.ts +2 -0
  335. package/dist/source-agent-qualifier.d.ts.map +1 -1
  336. package/dist/source-agent-qualifier.js +36 -36
  337. package/dist/standup/build.js +25 -25
  338. package/dist/storage/buffer-drain-claims.d.ts +18 -6
  339. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  340. package/dist/storage/buffer-drain-claims.js +2 -2
  341. package/dist/storage/buffer-session-files.js +2 -2
  342. package/dist/storage/content-hash-index.d.ts +8 -0
  343. package/dist/storage/content-hash-index.d.ts.map +1 -1
  344. package/dist/storage.d.ts +10 -1
  345. package/dist/storage.d.ts.map +1 -1
  346. package/dist/storage.js +23 -23
  347. package/dist/subject-config.js +3 -3
  348. package/dist/summarizer.js +4 -4
  349. package/dist/support-passport/index.js +93 -91
  350. package/dist/tokens.js +3 -3
  351. package/dist/transfer/autodetect.js +1 -1
  352. package/dist/transfer/backup.js +2 -2
  353. package/dist/transfer/capsule-export.js +3 -3
  354. package/dist/transfer/capsule-gunzip.d.ts +14 -0
  355. package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
  356. package/dist/transfer/capsule-import.d.ts.map +1 -1
  357. package/dist/transfer/capsule-import.js +3 -2
  358. package/dist/transfer/capsule-merge.d.ts.map +1 -1
  359. package/dist/transfer/export-okf.js +24 -24
  360. package/dist/verified-recall.js +24 -24
  361. package/dist/who-knows-cli.js +26 -26
  362. package/dist/who-knows.js +25 -25
  363. package/dist/write-envelope.js +3 -3
  364. package/package.json +2 -2
  365. package/src/access-boundary.ts +15 -0
  366. package/src/access-errors.ts +22 -0
  367. package/src/access-fleet-wide-work-peer.test.ts +236 -0
  368. package/src/access-http-peer-fleet-wide.test.ts +192 -0
  369. package/src/access-http.test.ts +249 -4
  370. package/src/access-http.ts +114 -39
  371. package/src/access-mcp-frames.test.ts +47 -0
  372. package/src/access-mcp-frames.ts +74 -0
  373. package/src/access-mcp.test.ts +210 -0
  374. package/src/access-mcp.ts +47 -37
  375. package/src/access-observe-replay-durability.test.ts +40 -4
  376. package/src/access-observe-write-surface.ts +4 -4
  377. package/src/access-operations-batch.ts +23 -12
  378. package/src/access-proto-injection.test.ts +340 -0
  379. package/src/access-schema.ts +31 -5
  380. package/src/access-service-capsule-import.test.ts +235 -0
  381. package/src/access-service.ts +6 -45
  382. package/src/backlog-scheduler.test.ts +443 -3
  383. package/src/briefing-deadline.ts +5 -4
  384. package/src/buffer-admission.ts +45 -0
  385. package/src/buffer-eviction-backpressure.test.ts +211 -0
  386. package/src/buffer-eviction.ts +8 -14
  387. package/src/buffer-incremental-persistence.test.ts +25 -37
  388. package/src/buffer-persistence.ts +15 -6
  389. package/src/buffer-session.test.ts +32 -16
  390. package/src/buffer-turn-helpers.ts +47 -0
  391. package/src/buffer-types.ts +28 -1
  392. package/src/buffer.ts +83 -91
  393. package/src/capsule-import-gate.ts +109 -0
  394. package/src/cli.ts +4 -4
  395. package/src/extraction-quote-grounding.test.ts +125 -0
  396. package/src/extraction-quote-grounding.ts +99 -25
  397. package/src/extraction-source-grounding-rules.ts +6 -1
  398. package/src/extraction-source-grounding.ts +2 -0
  399. package/src/extraction.ts +3 -4
  400. package/src/fact-dedup-window.test.ts +103 -20
  401. package/src/namespaces/search.test.ts +480 -1
  402. package/src/namespaces/search.ts +93 -10
  403. package/src/orchestration/backlog-scheduler.ts +13 -12
  404. package/src/orchestration/buffer-drain-runner.ts +40 -16
  405. package/src/orchestration/extraction-persist.ts +2 -10
  406. package/src/orchestration/fact-dedup-window.ts +5 -12
  407. package/src/orchestration/turn-ingestion.ts +38 -39
  408. package/src/recall-navigation-operations.ts +9 -2
  409. package/src/source-agent-qualifier.ts +3 -0
  410. package/src/storage/buffer-drain-claims.test.ts +330 -6
  411. package/src/storage/buffer-drain-claims.ts +226 -80
  412. package/src/storage/buffer-session-files.ts +2 -2
  413. package/src/storage/content-hash-index.ts +9 -0
  414. package/src/storage.ts +15 -0
  415. package/src/transfer/capsule-gunzip.ts +39 -0
  416. package/src/transfer/capsule-import.test.ts +73 -0
  417. package/src/transfer/capsule-import.ts +2 -2
  418. package/src/transfer/capsule-merge.test.ts +34 -0
  419. package/src/transfer/capsule-merge.ts +2 -10
  420. package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
  421. package/dist/chunk-2XJZLSXE.js.map +0 -1
  422. package/dist/chunk-3JPTUCD7.js.map +0 -1
  423. package/dist/chunk-4KLQI3L2.js.map +0 -1
  424. package/dist/chunk-6LCWKS62.js.map +0 -1
  425. package/dist/chunk-77R7BQZ2.js.map +0 -1
  426. package/dist/chunk-7PMOCQOL.js.map +0 -1
  427. package/dist/chunk-B74NPUHT.js.map +0 -1
  428. package/dist/chunk-D5SSBCYY.js.map +0 -1
  429. package/dist/chunk-HLKUZW42.js.map +0 -1
  430. package/dist/chunk-HWHNQVRV.js.map +0 -1
  431. package/dist/chunk-KASJJGRG.js.map +0 -1
  432. package/dist/chunk-KQUSP6LB.js.map +0 -1
  433. package/dist/chunk-LGEH473S.js.map +0 -1
  434. package/dist/chunk-LU66I56Q.js.map +0 -1
  435. package/dist/chunk-MPDLLLOG.js.map +0 -1
  436. package/dist/chunk-MPGLM2VU.js.map +0 -1
  437. package/dist/chunk-QZBTIYKD.js.map +0 -1
  438. package/dist/chunk-VLMBSGQF.js.map +0 -1
  439. package/dist/chunk-WAD32CZG.js.map +0 -1
  440. package/dist/chunk-WC475JOG.js.map +0 -1
  441. package/dist/chunk-X2KO4FK5.js.map +0 -1
  442. package/dist/chunk-XYP5WBDO.js.map +0 -1
  443. package/dist/chunk-YK7GOPIE.js.map +0 -1
  444. package/dist/chunk-Z3DDQXM4.js.map +0 -1
  445. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
  446. /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
  447. /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → buffer-admission.js.map} +0 -0
  448. /package/dist/{resolution-BNWE5Y25.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  449. /package/dist/{chunk-MSIRVXQ3.js.map → chunk-2G4TV2ML.js.map} +0 -0
  450. /package/dist/{chunk-IR4C3DP2.js.map → chunk-2K3AUDH7.js.map} +0 -0
  451. /package/dist/{chunk-2IW5PT7U.js.map → chunk-2N4OUSIA.js.map} +0 -0
  452. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
  453. /package/dist/{chunk-QZSIDSB2.js.map → chunk-2NUDHVYM.js.map} +0 -0
  454. /package/dist/{chunk-HPFVZPRH.js.map → chunk-2U4LMSEX.js.map} +0 -0
  455. /package/dist/{chunk-WHQXWFH7.js.map → chunk-2XOFVTTT.js.map} +0 -0
  456. /package/dist/{chunk-I6ORDT54.js.map → chunk-32CA7B6L.js.map} +0 -0
  457. /package/dist/{chunk-T77AX6DG.js.map → chunk-32YX7EJ3.js.map} +0 -0
  458. /package/dist/{chunk-HLWXCDUQ.js.map → chunk-3DAWHP5K.js.map} +0 -0
  459. /package/dist/{chunk-ZJJFUEPO.js.map → chunk-3PJEDHLD.js.map} +0 -0
  460. /package/dist/{chunk-PJYK7HH2.js.map → chunk-3UZ2VMKD.js.map} +0 -0
  461. /package/dist/{chunk-IN4OFAHX.js.map → chunk-3Y5X6YFH.js.map} +0 -0
  462. /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
  463. /package/dist/{chunk-T6T2RMWW.js.map → chunk-4GMI5HFH.js.map} +0 -0
  464. /package/dist/{chunk-YOEYL6CH.js.map → chunk-4LCVA2LW.js.map} +0 -0
  465. /package/dist/{chunk-66JOLMWS.js.map → chunk-4X7HQKAG.js.map} +0 -0
  466. /package/dist/{chunk-65SKM4BF.js.map → chunk-4XYAWK5H.js.map} +0 -0
  467. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  468. /package/dist/{chunk-TPX7XXY4.js.map → chunk-5SOSQTVE.js.map} +0 -0
  469. /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
  470. /package/dist/{chunk-BK7AJUWB.js.map → chunk-6BCWFCDG.js.map} +0 -0
  471. /package/dist/{chunk-V27FK6D6.js.map → chunk-6XTUZKCZ.js.map} +0 -0
  472. /package/dist/{chunk-LECSS7EE.js.map → chunk-73UZZFJC.js.map} +0 -0
  473. /package/dist/{chunk-4EACCIPO.js.map → chunk-77PDJ4OE.js.map} +0 -0
  474. /package/dist/{chunk-GQZSGXR6.js.map → chunk-7GSL4LBG.js.map} +0 -0
  475. /package/dist/{chunk-B6SPPYGJ.js.map → chunk-7IWRLYFJ.js.map} +0 -0
  476. /package/dist/{chunk-R45L4TV6.js.map → chunk-7LCGFUMB.js.map} +0 -0
  477. /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
  478. /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
  479. /package/dist/{chunk-ZXF3O7P4.js.map → chunk-ARRTLPRC.js.map} +0 -0
  480. /package/dist/{chunk-S5CQI2KF.js.map → chunk-B3GKQ6DU.js.map} +0 -0
  481. /package/dist/{chunk-XRBHIR7Q.js.map → chunk-B56LORLY.js.map} +0 -0
  482. /package/dist/{chunk-KO3YBHOV.js.map → chunk-BIVKPQ6Q.js.map} +0 -0
  483. /package/dist/{chunk-54FDG7K7.js.map → chunk-C2HORX76.js.map} +0 -0
  484. /package/dist/{chunk-E33UJHY3.js.map → chunk-CN7Q3T2J.js.map} +0 -0
  485. /package/dist/{chunk-YUTG35M4.js.map → chunk-CYCEROPR.js.map} +0 -0
  486. /package/dist/{chunk-TFLPDNQV.js.map → chunk-D5EV6Y2L.js.map} +0 -0
  487. /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
  488. /package/dist/{chunk-B4ZU34JQ.js.map → chunk-DT7SNJH6.js.map} +0 -0
  489. /package/dist/{chunk-FVRTCYZ3.js.map → chunk-EA2LA5RI.js.map} +0 -0
  490. /package/dist/{chunk-WBN66HPI.js.map → chunk-ELHJK7LM.js.map} +0 -0
  491. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  492. /package/dist/{chunk-YAUHSNPB.js.map → chunk-F74G4KIH.js.map} +0 -0
  493. /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
  494. /package/dist/{chunk-KESJ5XX3.js.map → chunk-G54BOKZ6.js.map} +0 -0
  495. /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
  496. /package/dist/{chunk-OQSSQOKQ.js.map → chunk-GULUWOA5.js.map} +0 -0
  497. /package/dist/{chunk-K6RTQZA4.js.map → chunk-HFUKXOUW.js.map} +0 -0
  498. /package/dist/{chunk-WV254W4V.js.map → chunk-HLN2HOXX.js.map} +0 -0
  499. /package/dist/{chunk-FUOAIJKQ.js.map → chunk-IBDWYRDR.js.map} +0 -0
  500. /package/dist/{chunk-AJG2TAMR.js.map → chunk-IEEV27BC.js.map} +0 -0
  501. /package/dist/{chunk-LTDLJCMQ.js.map → chunk-IJ3OEECW.js.map} +0 -0
  502. /package/dist/{chunk-BK4FBHZ3.js.map → chunk-IPD5GC7C.js.map} +0 -0
  503. /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
  504. /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-JM2YZKCQ.js.map} +0 -0
  505. /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
  506. /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
  507. /package/dist/{chunk-XHGYZRVW.js.map → chunk-NIVKG3DS.js.map} +0 -0
  508. /package/dist/{chunk-VZELUMKK.js.map → chunk-NQP4ZYGG.js.map} +0 -0
  509. /package/dist/{chunk-YO2JEX2Z.js.map → chunk-OGBQMY46.js.map} +0 -0
  510. /package/dist/{chunk-TLHF75OR.js.map → chunk-OL553AWT.js.map} +0 -0
  511. /package/dist/{chunk-CLVILSBR.js.map → chunk-OZNBZJ3S.js.map} +0 -0
  512. /package/dist/{chunk-VUROWXFL.js.map → chunk-P452SEKO.js.map} +0 -0
  513. /package/dist/{chunk-JX3E4XCW.js.map → chunk-PDOOUXCS.js.map} +0 -0
  514. /package/dist/{chunk-6QYPL6NI.js.map → chunk-PKZ274PI.js.map} +0 -0
  515. /package/dist/{chunk-KJCL2FZO.js.map → chunk-QD5R6U5J.js.map} +0 -0
  516. /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
  517. /package/dist/{chunk-VKDAJJQP.js.map → chunk-QEZ3DW5O.js.map} +0 -0
  518. /package/dist/{chunk-WYPCJK6K.js.map → chunk-QKERFEJS.js.map} +0 -0
  519. /package/dist/{chunk-BDN3TDGV.js.map → chunk-QKES7GU6.js.map} +0 -0
  520. /package/dist/{chunk-PCQWKMOL.js.map → chunk-QNSZYTSG.js.map} +0 -0
  521. /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
  522. /package/dist/{chunk-NUSAAN75.js.map → chunk-ROKDHO74.js.map} +0 -0
  523. /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
  524. /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
  525. /package/dist/{chunk-XT45WCKX.js.map → chunk-SYKJBS7S.js.map} +0 -0
  526. /package/dist/{chunk-5BE3FLKL.js.map → chunk-TAUHAP5J.js.map} +0 -0
  527. /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
  528. /package/dist/{chunk-K3B72DKJ.js.map → chunk-TRMZHGIX.js.map} +0 -0
  529. /package/dist/{chunk-WBDIZWJQ.js.map → chunk-TSS3D7ZY.js.map} +0 -0
  530. /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
  531. /package/dist/{chunk-GGJFRVRD.js.map → chunk-US7KPQA5.js.map} +0 -0
  532. /package/dist/{chunk-FJ2VJTGA.js.map → chunk-UWRHHRDK.js.map} +0 -0
  533. /package/dist/{chunk-SDESKFKT.js.map → chunk-UYHDMCY2.js.map} +0 -0
  534. /package/dist/{chunk-3T3VHXUH.js.map → chunk-VKILERSL.js.map} +0 -0
  535. /package/dist/{chunk-2TGIKMMZ.js.map → chunk-WXDTOE6F.js.map} +0 -0
  536. /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
  537. /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
  538. /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
  539. /package/dist/{chunk-QA3Y2O6H.js.map → chunk-YAVIBVCU.js.map} +0 -0
  540. /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
  541. /package/dist/{chunk-EBOHFZI4.js.map → chunk-YRZPBN46.js.map} +0 -0
  542. /package/dist/{chunk-FMFCQRDM.js.map → chunk-ZANNIXXE.js.map} +0 -0
  543. /package/dist/{chunk-HQGATKG2.js.map → chunk-ZBFLV657.js.map} +0 -0
  544. /package/dist/{chunk-6L63IN3M.js.map → chunk-ZOJUWVAU.js.map} +0 -0
  545. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -13,8 +13,8 @@ import {
13
13
  import { peerManifestRevision } from "./reconcile/manifest.js";
14
14
  import {
15
15
  EngramAccessInputError,
16
+ EngramAccessService,
16
17
  type EngramAccessRecallRequest,
17
- type EngramAccessService,
18
18
  type EngramAccessBriefingRequest,
19
19
  } from "./access-service.js";
20
20
  import {
@@ -815,6 +815,48 @@ test("HTTP coding-context endpoint accepts projectTag shorthand", async () => {
815
815
  }
816
816
  });
817
817
 
818
+ test("HTTP observe answers 503 buffer_admission_refused when durable capture hits the entry cap", async () => {
819
+ const refusal = new Error("buffer at session-entry cap");
820
+ refusal.name = "ReplayAdmissionRefusedError";
821
+ // SAFETY: this fixture drives only the observe route; the remaining service members are unreachable.
822
+ const service = Object.assign({} as EngramAccessService, {
823
+ observe: async () => {
824
+ throw refusal;
825
+ },
826
+ });
827
+ const server = new EngramAccessHttpServer({
828
+ service,
829
+ port: 0,
830
+ authToken: "test-token",
831
+ adminConsoleEnabled: false,
832
+ });
833
+
834
+ const status = await server.start();
835
+ try {
836
+ const response = await fetch(
837
+ `http://127.0.0.1:${status.port}/engram/v1/observe`,
838
+ {
839
+ method: "POST",
840
+ headers: {
841
+ authorization: "Bearer test-token",
842
+ "content-type": "application/json",
843
+ },
844
+ body: JSON.stringify({
845
+ sessionKey: "capacity-test-session",
846
+ messages: [{ role: "user", content: "retry me" }],
847
+ }),
848
+ },
849
+ );
850
+ assert.equal(response.status, 503, "admission backpressure is retryable, not a server fault");
851
+ assert.deepEqual(await response.json(), {
852
+ error: "buffer at session-entry cap",
853
+ code: "buffer_admission_refused",
854
+ });
855
+ } finally {
856
+ await server.stop();
857
+ }
858
+ });
859
+
818
860
  test("HTTP briefing endpoint dispatches through the briefing operation", async () => {
819
861
  const calls: unknown[] = [];
820
862
  const service = {
@@ -2996,11 +3038,11 @@ test("HTTP scoped-token namespace allow-list is enforced on every namespace rout
2996
3038
  "scoped memory_get: the allowed namespace reaches the service",
2997
3039
  );
2998
3040
 
2999
- // ── a non-namespace-scoped route is unaffected by namespace scoping ──
3041
+ // ── a global-store route is fleet-wide: a namespace-scoped token is denied (issue #3225 B3) ──
3000
3042
  assert.equal(
3001
3043
  (await request("scoped-ns-a", "/engram/v1/peers")).status,
3002
- 200,
3003
- "a non-namespace-scoped route must be unaffected",
3044
+ 403,
3045
+ "peer routes touch global stores, so a namespace-scoped token is denied",
3004
3046
  );
3005
3047
 
3006
3048
  // ── unrestricted token: omitting namespace still reaches the default tenant ──
@@ -6515,3 +6557,206 @@ test("a budget above the backend cap still gets the rows it asked for", async ()
6515
6557
  "the excluded hit was replaced, not subtracted",
6516
6558
  );
6517
6559
  });
6560
+
6561
+ /**
6562
+ * Prototype-backed service stub for the write-quota tests: the constructor's
6563
+ * surface getters read only `orchestrator.config`, and each test overrides the
6564
+ * single member its route dispatches.
6565
+ */
6566
+ function quotaStubService(): EngramAccessService {
6567
+ // SAFETY: the prototype supplies the untouched service surface; each quota test overrides only the member its route dispatches.
6568
+ const service = Object.create(EngramAccessService.prototype) as EngramAccessService;
6569
+ // SAFETY: startup reads only orchestrator.config from the service; an empty config keeps every surface flag false.
6570
+ Reflect.set(service, "orchestrator", { config: {} });
6571
+ return service;
6572
+ }
6573
+
6574
+ test("concurrent distinct-payload writes cannot overshoot the write quota (issue #3225 C4)", async () => {
6575
+ // Checking hasCapacity before the operation and calling record after it
6576
+ // left a window where N in-flight writes all pass against the same
6577
+ // committed count. Admission must reserve a slot, so only LIMIT of TOTAL
6578
+ // overlapping writes proceed and the rest get 429.
6579
+ const LIMIT = 2;
6580
+ const TOTAL = 6;
6581
+ let dispatches = 0;
6582
+ const firstDispatched = Promise.withResolvers<void>();
6583
+ const releaseWrites = Promise.withResolvers<void>();
6584
+ const service = quotaStubService();
6585
+ service.capsuleExport = async (request) => {
6586
+ dispatches += 1;
6587
+ if (dispatches === 1) firstDispatched.resolve();
6588
+ await releaseWrites.promise;
6589
+ // SAFETY: the route serializes this stub result into the HTTP response; the test asserts statuses, not this shape.
6590
+ return { ok: true, name: request.name } as never;
6591
+ };
6592
+ const server = new EngramAccessHttpServer({
6593
+ service,
6594
+ port: 0,
6595
+ authToken: "test-token",
6596
+ adminConsoleEnabled: false,
6597
+ writeRateLimitMaxRequests: LIMIT,
6598
+ });
6599
+ const status = await server.start();
6600
+ const post = (index: number) =>
6601
+ fetch(`http://127.0.0.1:${status.port}/engram/v1/capsules/export`, {
6602
+ method: "POST",
6603
+ headers: {
6604
+ authorization: "Bearer test-token",
6605
+ "content-type": "application/json",
6606
+ },
6607
+ body: JSON.stringify({ name: `quota-race-${index}` }),
6608
+ });
6609
+ try {
6610
+ const first = post(0);
6611
+ await firstDispatched.promise;
6612
+ const rest = Array.from({ length: TOTAL - 1 }, (_, index) => post(index + 1));
6613
+ // The first write is admitted and held open inside the service, so every
6614
+ // other request must run its admission check against an unrecorded
6615
+ // window. Loopback arrival needs a bounded grace; the overlap cannot be
6616
+ // driven deterministically because admission is server-internal.
6617
+ await new Promise((resolve) => setTimeout(resolve, 50));
6618
+ releaseWrites.resolve();
6619
+ const responses = await Promise.all([first, ...rest]);
6620
+ const statuses = responses.map((response) => response.status).sort((a, b) => a - b);
6621
+ assert.deepEqual(
6622
+ statuses,
6623
+ [...Array<number>(LIMIT).fill(200), ...Array<number>(TOTAL - LIMIT).fill(429)],
6624
+ `expected exactly ${LIMIT} admitted writes and ${TOTAL - LIMIT} rejections, got ${JSON.stringify(statuses)}`,
6625
+ );
6626
+ } finally {
6627
+ releaseWrites.resolve();
6628
+ await server.stop();
6629
+ }
6630
+ });
6631
+
6632
+ test("a failed write releases its reserved quota slot (issue #3225 C4)", async () => {
6633
+ let calls = 0;
6634
+ const service = quotaStubService();
6635
+ service.capsuleExport = async (request) => {
6636
+ calls += 1;
6637
+ if (calls === 1) throw new Error("export backend failed");
6638
+ // SAFETY: the route serializes this stub result into the HTTP response; the test asserts statuses, not this shape.
6639
+ return { ok: true, name: request.name } as never;
6640
+ };
6641
+ const server = new EngramAccessHttpServer({
6642
+ service,
6643
+ port: 0,
6644
+ authToken: "test-token",
6645
+ adminConsoleEnabled: false,
6646
+ writeRateLimitMaxRequests: 1,
6647
+ });
6648
+ const status = await server.start();
6649
+ const post = (name: string) =>
6650
+ fetch(`http://127.0.0.1:${status.port}/engram/v1/capsules/export`, {
6651
+ method: "POST",
6652
+ headers: {
6653
+ authorization: "Bearer test-token",
6654
+ "content-type": "application/json",
6655
+ },
6656
+ body: JSON.stringify({ name }),
6657
+ });
6658
+ try {
6659
+ // The first export fails in the backend; the slot it reserved at
6660
+ // admission must be released, so the retry is admitted and only then
6661
+ // does the window fill.
6662
+ assert.equal((await post("quota-fail-1")).status, 500);
6663
+ assert.equal((await post("quota-fail-2")).status, 200);
6664
+ assert.equal((await post("quota-fail-3")).status, 429);
6665
+ } finally {
6666
+ await server.stop();
6667
+ }
6668
+ });
6669
+
6670
+ test("a replayed observe never 429s and never consumes a second quota slot (issue #1434, #3225 C4)", async () => {
6671
+ const stored = new Map<string, { sessionKey: string }>();
6672
+ let hookCalls = 0;
6673
+ const service = quotaStubService();
6674
+ service.observe = async (request, hooks) => {
6675
+ const key = request.idempotencyKey?.trim();
6676
+ const cached = key ? stored.get(key) : undefined;
6677
+ if (cached) {
6678
+ // SAFETY: the route only forwards this result into the HTTP response and reads its dryRun/idempotencyReplay flags.
6679
+ return { ...cached, idempotencyReplay: true } as never;
6680
+ }
6681
+ // Mirror the real service contract: the quota hook fires exactly once,
6682
+ // immediately before an actual write commits, never on a replay.
6683
+ await hooks?.enforceWriteQuota?.();
6684
+ hookCalls += 1;
6685
+ const response = { sessionKey: request.sessionKey };
6686
+ stored.set(key ?? "", response);
6687
+ // SAFETY: the route only forwards this result into the HTTP response and reads its dryRun/idempotencyReplay flags.
6688
+ return response as never;
6689
+ };
6690
+ const server = new EngramAccessHttpServer({
6691
+ service,
6692
+ port: 0,
6693
+ authToken: "test-token",
6694
+ adminConsoleEnabled: false,
6695
+ writeRateLimitMaxRequests: 1,
6696
+ });
6697
+ const status = await server.start();
6698
+ const post = (idempotencyKey: string) =>
6699
+ fetch(`http://127.0.0.1:${status.port}/engram/v1/observe`, {
6700
+ method: "POST",
6701
+ headers: {
6702
+ authorization: "Bearer test-token",
6703
+ "content-type": "application/json",
6704
+ },
6705
+ body: JSON.stringify({
6706
+ sessionKey: "quota-replay",
6707
+ messages: [{ role: "user", content: "hello" }],
6708
+ idempotencyKey,
6709
+ }),
6710
+ });
6711
+ try {
6712
+ assert.equal((await post("quota-replay-1")).status, 202);
6713
+ const replay = await post("quota-replay-1");
6714
+ assert.equal(replay.status, 202);
6715
+ // SAFETY: our own server serialized this body; the test reads only the idempotencyReplay flag.
6716
+ const replayBody = (await replay.json()) as { idempotencyReplay?: boolean };
6717
+ assert.equal(replayBody.idempotencyReplay, true);
6718
+ assert.equal(hookCalls, 1, "the replay must not re-run the quota hook");
6719
+ // The window still holds exactly the first write's slot: a different
6720
+ // payload is rejected, proving the replay consumed nothing.
6721
+ assert.equal((await post("quota-replay-2")).status, 429);
6722
+ } finally {
6723
+ await server.stop();
6724
+ }
6725
+ });
6726
+
6727
+ test("a dreams dry run neither 429s nor consumes quota at a full window (#3225 C4)", async () => {
6728
+ const service = quotaStubService();
6729
+ service.dreamsRun = async (options) => {
6730
+ // SAFETY: the route serializes this stub result into the HTTP response and reads its dryRun flag.
6731
+ return { phase: options.phase, dryRun: options.dryRun === true } as never;
6732
+ };
6733
+ const server = new EngramAccessHttpServer({
6734
+ service,
6735
+ port: 0,
6736
+ authToken: "test-token",
6737
+ adminConsoleEnabled: false,
6738
+ writeRateLimitMaxRequests: 1,
6739
+ });
6740
+ const status = await server.start();
6741
+ const post = (body: Record<string, boolean>) =>
6742
+ fetch(`http://127.0.0.1:${status.port}/engram/v1/dreams/run`, {
6743
+ method: "POST",
6744
+ headers: {
6745
+ authorization: "Bearer test-token",
6746
+ "content-type": "application/json",
6747
+ },
6748
+ body: JSON.stringify({ phase: "lightSleep", ...body }),
6749
+ });
6750
+ try {
6751
+ assert.equal((await post({})).status, 200);
6752
+ // The window is full; the dry run must pass and leave the count alone.
6753
+ const dry = await post({ dryRun: true });
6754
+ assert.equal(dry.status, 200);
6755
+ // SAFETY: our own server serialized this body; the test reads only the dryRun flag.
6756
+ const dryBody = (await dry.json()) as { dryRun?: boolean };
6757
+ assert.equal(dryBody.dryRun, true);
6758
+ assert.equal((await post({})).status, 429);
6759
+ } finally {
6760
+ await server.stop();
6761
+ }
6762
+ });
@@ -417,6 +417,11 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
417
417
  private readonly resourceMetadataUrl?: string;
418
418
  private readonly externalRequestHandler?: SupportPassportExternalRequestHandler;
419
419
  private readonly writeLimiter: WriteRateLimiter;
420
+ // Reservations taken by ensureWriteRateLimitAvailable and not yet committed
421
+ // by recordWriteRateLimitHit, keyed by request. The per-request release in
422
+ // the connection callback covers every early return and error path, so a
423
+ // failed or exempt write never leaks its slot (issue #3225 C4).
424
+ private readonly pendingWriteQuotas = new WeakMap<IncomingMessage, WriteRateLimitReservation>();
420
425
  private readonly mcpServer: EngramMcpServer;
421
426
  private server: Server | null = null;
422
427
  private boundPort = 0;
@@ -544,6 +549,13 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
544
549
  });
545
550
  return;
546
551
  }
552
+ if (err.name === "ReplayAdmissionRefusedError") {
553
+ // Durable-capture backpressure (#3251/#1908): the request was
554
+ // never accepted — the buffer is at entry capacity. Retryable,
555
+ // not a server fault (503, not 500).
556
+ this.respondJson(res, 503, { error: err.message, code: "buffer_admission_refused" });
557
+ return;
558
+ }
547
559
  if (res.headersSent) {
548
560
  res.destroy(err as Error);
549
561
  return;
@@ -556,6 +568,9 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
556
568
  }).finally(() => {
557
569
  req.off("aborted", abortDisconnectedRequest);
558
570
  res.off("close", abortDisconnectedRequest);
571
+ // A counted write that failed or was exempt (dry run, replay) must
572
+ // not hold its reserved slot past the request (issue #3225 C4).
573
+ this.releasePendingWriteQuota(req);
559
574
  });
560
575
  });
561
576
  });
@@ -2739,26 +2754,38 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
2739
2754
  // DELETE /engram/v1/peers/:id?forget=true — destructive full purge (issue #679 completion)
2740
2755
  // GET /engram/v1/peers/:id/profile — get peer profile
2741
2756
  if (req.method === "GET" && pathname === "/engram/v1/peers") {
2742
- this.enforceTokenOp("peer_list"); // boundary dispatch (issue #1525)
2743
- const result = await this.service.peerList();
2744
- this.respondJson(res, 200, result);
2757
+ const op = getOperation("peer_list"); // boundary dispatch (issue #1525)
2758
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_list");
2759
+ // SAFETY: the registered peer_list handler returns { result } from the service.
2760
+ const output = (await op.run({}, {
2761
+ service: this.service,
2762
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2763
+ sourceConnector: this.resolveConnector(req),
2764
+ })) as { result: unknown };
2765
+ this.respondJson(res, 200, output.result);
2745
2766
  return;
2746
2767
  }
2747
2768
 
2748
2769
  const peerProfileMatch = /^\/engram\/v1\/peers\/([^/]+)\/profile$/.exec(pathname);
2749
2770
  if (peerProfileMatch) {
2750
- this.enforceTokenOp("peer_profile_get"); // boundary dispatch (issue #1525)
2751
2771
  if (req.method !== "GET") {
2752
2772
  this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
2753
2773
  return;
2754
2774
  }
2775
+ const op = getOperation("peer_profile_get"); // boundary dispatch (issue #1525)
2776
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_profile_get");
2755
2777
  const peerId = decodePeerIdSegment(peerProfileMatch[1] ?? "");
2756
- const result = await this.service.peerProfileGet(peerId);
2757
- if (!result.found) {
2778
+ // SAFETY: the registered peer_profile_get handler returns { result } from the service.
2779
+ const output = (await op.run({ id: peerId }, {
2780
+ service: this.service,
2781
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2782
+ sourceConnector: this.resolveConnector(req),
2783
+ })) as { result: { found: boolean } };
2784
+ if (!output.result.found) {
2758
2785
  this.respondJson(res, 404, { error: "peer_profile_not_found", code: "peer_profile_not_found" });
2759
2786
  return;
2760
2787
  }
2761
- this.respondJson(res, 200, result);
2788
+ this.respondJson(res, 200, output.result);
2762
2789
  return;
2763
2790
  }
2764
2791
 
@@ -2767,47 +2794,49 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
2767
2794
  const peerId = decodePeerIdSegment(peerIdMatch[1] ?? "");
2768
2795
 
2769
2796
  if (req.method === "GET") {
2770
- this.enforceTokenOp("peer_get"); // boundary dispatch (issue #1525)
2771
- const result = await this.service.peerGet(peerId);
2772
- if (!result.found) {
2797
+ const op = getOperation("peer_get"); // boundary dispatch (issue #1525)
2798
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_get");
2799
+ // SAFETY: the registered peer_get handler returns { result } with a found flag.
2800
+ const output = (await op.run({ id: peerId }, {
2801
+ service: this.service,
2802
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2803
+ sourceConnector: this.resolveConnector(req),
2804
+ })) as { result: { found: boolean } };
2805
+ if (!output.result.found) {
2773
2806
  this.respondJson(res, 404, { error: "peer_not_found", code: "peer_not_found" });
2774
2807
  return;
2775
2808
  }
2776
- this.respondJson(res, 200, result);
2809
+ this.respondJson(res, 200, output.result);
2777
2810
  return;
2778
2811
  }
2779
2812
 
2780
2813
  if (req.method === "PUT") {
2781
- this.enforceTokenOp("peer_set"); // boundary dispatch (issue #1525)
2782
2814
  const body = await this.readJsonBody(req) as Record<string, unknown>;
2783
- // Reject malformed types up front rather than silently dropping them
2784
- // to undefined and letting peerSet fall back to defaults
2785
- // (CLAUDE.md rule 51: no silent defaults on bad input).
2786
- if ("kind" in body && body.kind !== undefined && typeof body.kind !== "string") {
2787
- throw new EngramAccessInputError("kind must be a string when provided");
2788
- }
2789
- if (
2790
- "displayName" in body &&
2791
- body.displayName !== undefined &&
2792
- typeof body.displayName !== "string"
2793
- ) {
2794
- throw new EngramAccessInputError("displayName must be a string when provided");
2795
- }
2796
- if ("notes" in body && body.notes !== undefined && typeof body.notes !== "string") {
2797
- throw new EngramAccessInputError("notes must be a string when provided");
2815
+ const op = getOperation("peer_set"); // boundary dispatch (issue #1525)
2816
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_set");
2817
+ // The operation schema strips nulls as "absent", so an explicit null
2818
+ // must fail here or the peer would register with default fields.
2819
+ for (const field of ["kind", "displayName", "notes"]) {
2820
+ if (body[field] === null) throw new EngramAccessInputError(`${field} must be a string when provided`);
2798
2821
  }
2799
- const result = await this.service.peerSet({
2822
+ // Raw body fields: the operation schema rejects malformed types with an
2823
+ // input error instead of silently defaulting them (rule 51).
2824
+ // SAFETY: the registered peer_set handler returns { result } with the upsert outcome.
2825
+ const output = (await op.run({
2800
2826
  id: peerId,
2801
- kind: typeof body.kind === "string" ? body.kind : undefined,
2802
- displayName: typeof body.displayName === "string" ? body.displayName : undefined,
2803
- notes: typeof body.notes === "string" ? body.notes : undefined,
2804
- });
2805
- this.respondJson(res, result.created ? 201 : 200, result);
2827
+ kind: body.kind,
2828
+ displayName: body.displayName,
2829
+ notes: body.notes,
2830
+ }, {
2831
+ service: this.service,
2832
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2833
+ sourceConnector: this.resolveConnector(req),
2834
+ })) as { result: { created: boolean } };
2835
+ this.respondJson(res, output.result.created ? 201 : 200, output.result);
2806
2836
  return;
2807
2837
  }
2808
2838
 
2809
2839
  if (req.method === "DELETE") {
2810
- this.enforceTokenOp("peer_delete"); // boundary dispatch (issue #1525)
2811
2840
  // `?forget=true` triggers the destructive full-purge path (issue #679
2812
2841
  // completion). The caller must also pass `confirm=yes` in the request
2813
2842
  // body; absent confirmation yields 400. Plain DELETE (no ?forget) keeps
@@ -2824,12 +2853,26 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
2824
2853
  });
2825
2854
  return;
2826
2855
  }
2827
- const result = await this.service.peerForget(peerId, { confirm: "yes" });
2828
- this.respondJson(res, 200, result);
2856
+ const op = getOperation("peer_forget"); // boundary dispatch (issue #1525)
2857
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_forget");
2858
+ // SAFETY: the registered peer_forget handler returns { result } with the purge outcome.
2859
+ const output = (await op.run({ id: peerId, confirm: "yes" }, {
2860
+ service: this.service,
2861
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2862
+ sourceConnector: this.resolveConnector(req),
2863
+ })) as { result: unknown };
2864
+ this.respondJson(res, 200, output.result);
2829
2865
  return;
2830
2866
  }
2831
- const result = await this.service.peerDelete(peerId);
2832
- this.respondJson(res, 200, result);
2867
+ const op = getOperation("peer_delete"); // boundary dispatch (issue #1525)
2868
+ if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_delete");
2869
+ // SAFETY: the registered peer_delete handler returns { result } with the delete outcome.
2870
+ const output = (await op.run({ id: peerId }, {
2871
+ service: this.service,
2872
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
2873
+ sourceConnector: this.resolveConnector(req),
2874
+ })) as { result: unknown };
2875
+ this.respondJson(res, 200, output.result);
2833
2876
  return;
2834
2877
  }
2835
2878
 
@@ -3931,16 +3974,48 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
3931
3974
  return 200;
3932
3975
  }
3933
3976
 
3977
+ /**
3978
+ * Admit a quota-counted write by RESERVING a limiter slot for the request
3979
+ * (issue #3225 C4). A bare hasCapacity check admits N concurrent requests
3980
+ * against the same committed count; the reservation holds in-flight
3981
+ * capacity until the write commits at recordWriteRateLimitHit or the
3982
+ * request ends. Idempotent per request, so a quota hook that fires after
3983
+ * an earlier reservation is a no-op.
3984
+ */
3934
3985
  protected ensureWriteRateLimitAvailable(req?: IncomingMessage): void {
3935
- if (!this.writeLimiter.hasCapacity(this.principalForRateLimit(req))) {
3986
+ if (!req) {
3987
+ if (!this.writeLimiter.hasCapacity()) {
3988
+ throw new HttpError(429, "write_rate_limited", "write_rate_limited");
3989
+ }
3990
+ return;
3991
+ }
3992
+ if (this.pendingWriteQuotas.has(req)) return;
3993
+ const reservation = this.writeLimiter.reserve(this.principalForRateLimit(req));
3994
+ if (!reservation) {
3936
3995
  throw new HttpError(429, "write_rate_limited", "write_rate_limited");
3937
3996
  }
3997
+ this.pendingWriteQuotas.set(req, reservation);
3938
3998
  }
3939
3999
 
4000
+ /** Commit a counted write at the durable boundary; consumes the request's reservation. */
3940
4001
  protected recordWriteRateLimitHit(req?: IncomingMessage): void {
4002
+ const pending = req ? this.pendingWriteQuotas.get(req) : undefined;
4003
+ if (pending && req) {
4004
+ this.pendingWriteQuotas.delete(req);
4005
+ pending.commit();
4006
+ return;
4007
+ }
3941
4008
  this.writeLimiter.record(this.principalForRateLimit(req));
3942
4009
  }
3943
4010
 
4011
+ /** Release an uncommitted reservation after a rejection, failure, or exempt outcome. */
4012
+ private releasePendingWriteQuota(req: IncomingMessage): void {
4013
+ const pending = this.pendingWriteQuotas.get(req);
4014
+ if (!pending) return;
4015
+ this.pendingWriteQuotas.delete(req);
4016
+ pending.release();
4017
+ }
4018
+
3944
4019
  protected reserveWriteRateLimitSlot(req?: IncomingMessage): WriteRateLimitReservation {
3945
4020
  const reservation = this.writeLimiter.reserve(this.principalForRateLimit(req));
3946
4021
  if (!reservation) {
@@ -0,0 +1,47 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { MCP_STDIO_MAX_FRAME_BYTES, McpFrameReader } from "./access-mcp-frames.js";
5
+
6
+ function frame(body: string): Buffer {
7
+ return Buffer.from(`Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`, "utf-8");
8
+ }
9
+
10
+ test("a frame that arrives in two reads is returned once, without re-reading the partial buffer", () => {
11
+ const reader = new McpFrameReader();
12
+ const whole = frame('{"id":1}');
13
+ reader.push(whole.subarray(0, 10));
14
+ assert.equal(reader.next(), null);
15
+ assert.equal(reader.hasUnread(), false);
16
+
17
+ reader.push(whole.subarray(10));
18
+ assert.equal(reader.hasUnread(), true);
19
+ assert.deepEqual(reader.next(), { kind: "body", text: '{"id":1}' });
20
+ assert.equal(reader.next(), null);
21
+ });
22
+
23
+ test("bytes that arrive after the reader stopped on an empty buffer are unread", () => {
24
+ const reader = new McpFrameReader();
25
+ reader.push(frame("first"));
26
+ assert.deepEqual(reader.next(), { kind: "body", text: "first" });
27
+ assert.equal(reader.next(), null);
28
+ assert.equal(reader.hasUnread(), false);
29
+
30
+ reader.push(frame("second"));
31
+ assert.equal(reader.hasUnread(), true);
32
+ assert.deepEqual(reader.next(), { kind: "body", text: "second" });
33
+ });
34
+
35
+ test("the body of an oversized frame is dropped across reads and the next frame is read", () => {
36
+ const reader = new McpFrameReader();
37
+ const declared = MCP_STDIO_MAX_FRAME_BYTES + 5;
38
+ reader.push(Buffer.from(`Content-Length: ${declared}\r\n\r\n${"A".repeat(100)}`, "utf-8"));
39
+ assert.deepEqual(reader.next(), { kind: "too-large" });
40
+ assert.equal(reader.next(), null);
41
+
42
+ reader.push(Buffer.from("B".repeat(declared - 100), "utf-8"));
43
+ assert.equal(reader.hasUnread(), false);
44
+
45
+ reader.push(frame("after"));
46
+ assert.deepEqual(reader.next(), { kind: "body", text: "after" });
47
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Content-Length framing for the MCP stdio transport.
3
+ *
4
+ * `push` appends raw bytes. `next` returns one frame at a time and `null` when
5
+ * the buffered bytes hold no complete frame yet. The header and the body are
6
+ * each bounded to MCP_STDIO_MAX_FRAME_BYTES, which matches the HTTP access
7
+ * default `maxBodyBytes`, so a peer cannot grow the buffer without limit.
8
+ */
9
+ export const MCP_STDIO_MAX_FRAME_BYTES = 131072;
10
+
11
+ export type McpFrame = { kind: "body"; text: string } | { kind: "too-large" };
12
+
13
+ const TOO_LARGE: McpFrame = { kind: "too-large" };
14
+
15
+ export class McpFrameReader {
16
+ private buffer = Buffer.alloc(0);
17
+ /** Body bytes of a refused oversized frame that have not arrived yet. */
18
+ private skipBytes = 0;
19
+ /** Buffer length when `next` last stopped on an incomplete frame. */
20
+ private waitingBytes = 0;
21
+
22
+ push(chunk: Buffer): void {
23
+ const dropped = Math.min(this.skipBytes, chunk.length);
24
+ this.skipBytes -= dropped;
25
+ const kept = chunk.subarray(dropped);
26
+ if (kept.length > 0) this.buffer = Buffer.concat([this.buffer, kept]);
27
+ }
28
+
29
+ /** True when bytes arrived after `next` last examined the buffer. */
30
+ hasUnread(): boolean {
31
+ return this.buffer.length > this.waitingBytes;
32
+ }
33
+
34
+ next(): McpFrame | null {
35
+ this.waitingBytes = 0;
36
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
37
+ if (headerEnd < 0) {
38
+ if (this.buffer.length <= MCP_STDIO_MAX_FRAME_BYTES) return this.wait();
39
+ return this.discardBuffer();
40
+ }
41
+ if (headerEnd > MCP_STDIO_MAX_FRAME_BYTES) return this.discardBuffer();
42
+
43
+ const headers = this.buffer.subarray(0, headerEnd).toString("utf-8").split("\r\n");
44
+ const lengthHeader = headers.find((line) => line.toLowerCase().startsWith("content-length:"));
45
+ const contentLength = lengthHeader ? Number.parseInt(lengthHeader.split(":")[1]?.trim() ?? "0", 10) : NaN;
46
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
47
+ this.buffer = Buffer.alloc(0);
48
+ return null;
49
+ }
50
+
51
+ const bodyStart = headerEnd + 4;
52
+ if (contentLength > MCP_STDIO_MAX_FRAME_BYTES) {
53
+ const rest = this.buffer.subarray(bodyStart);
54
+ this.skipBytes = Math.max(0, contentLength - rest.length);
55
+ this.buffer = rest.subarray(Math.min(contentLength, rest.length));
56
+ return TOO_LARGE;
57
+ }
58
+ const bodyEnd = bodyStart + contentLength;
59
+ if (this.buffer.length < bodyEnd) return this.wait();
60
+ const text = this.buffer.subarray(bodyStart, bodyEnd).toString("utf-8");
61
+ this.buffer = this.buffer.subarray(bodyEnd);
62
+ return { kind: "body", text };
63
+ }
64
+
65
+ private wait(): null {
66
+ this.waitingBytes = this.buffer.length;
67
+ return null;
68
+ }
69
+
70
+ private discardBuffer(): McpFrame {
71
+ this.buffer = Buffer.alloc(0);
72
+ return TOO_LARGE;
73
+ }
74
+ }