@remnic/core 10.0.85 → 10.0.87

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,6 +1,33 @@
1
1
  /** Types split out of buffer.ts (structural ratchet, issue #1995). */
2
2
 
3
- import type { BufferTurn } from "./types.js";
3
+ import type { BufferTurn, CodingContext, SignalLevel } from "./types.js";
4
+ import type { ResolvedScopeProfilePlan } from "./namespaces/scope-profiles.js";
5
+
6
+ export type TriggerDecision = "extract_now" | "extract_batch" | "keep_buffering";
7
+
8
+ export interface AddTurnOutcome {
9
+ decision: TriggerDecision;
10
+ extractionTurns?: BufferTurn[];
11
+ }
12
+
13
+ export interface AddTurnMutationResult {
14
+ decision: TriggerDecision;
15
+ signalLevel: SignalLevel;
16
+ priorTurns: BufferTurn[];
17
+ activeTurnsSnapshot: BufferTurn[];
18
+ retainedTurnsSnapshot: BufferTurn[];
19
+ turnCountInWindow: number;
20
+ }
21
+
22
+ export interface RetainedTurnCleanupOptions {
23
+ abortSignal?: AbortSignal;
24
+ deadlineMs?: number;
25
+ /** Keep cleanup within the accepted namespace. */
26
+ writeNamespaceOverride?: string;
27
+ /** Match the original profile and project binding, not just its write namespace. */
28
+ scopeProfileWritePlan?: ResolvedScopeProfilePlan | null;
29
+ codingContext?: CodingContext;
30
+ }
4
31
 
5
32
  /**
6
33
  * Optional surprise probe injected into `SmartBuffer`.
package/src/buffer.ts CHANGED
@@ -23,39 +23,32 @@ import {
23
23
  bufferTurnsEqual,
24
24
  copyBufferTurn,
25
25
  describeError,
26
+ entryActivityAt,
27
+ entryHoldsAcceptedCapture,
26
28
  hasPinnedCaptureScope,
27
29
  liveTurnsFromExtractionSnapshot,
28
30
  matchingQueuedExtractionPrefixLength,
31
+ normalizeBufferState,
29
32
  probeWithTimeout,
30
33
  selectRetainedDeferredTurns,
31
34
  type BufferEntryCounts,
32
35
  } from "./buffer-turn-helpers.js";
36
+ import { BufferAdmissionRefusedError, canAdmitNewEntryUnlocked } from "./buffer-admission.js";
37
+ export { BufferAdmissionRefusedError } from "./buffer-admission.js";
38
+ import type { AddTurnMutationResult, AddTurnOutcome, RetainedTurnCleanupOptions, TriggerDecision } from "./buffer-types.js";
33
39
 
34
- export type TriggerDecision = "extract_now" | "extract_batch" | "keep_buffering";
40
+ export type { TriggerDecision, AddTurnOutcome, RetainedTurnCleanupOptions } from "./buffer-types.js";
41
+
42
+ // Eviction hooks are pure functions of an entry; one shared instance serves
43
+ // pruneEntries, the admission probe and the batch preflight.
44
+ const EVICTION_HOOKS = { activityAt: entryActivityAt, holdsAcceptedCapture: entryHoldsAcceptedCapture };
35
45
 
36
46
  export type { BufferSurpriseProbe } from "./buffer-types.js";
37
47
  import type { BufferSurpriseProbe, SurpriseTelemetryQueueEntry } from "./buffer-types.js";
38
48
 
39
- export interface AddTurnOutcome {
40
- decision: TriggerDecision;
41
- extractionTurns?: BufferTurn[];
42
- }
43
-
44
- export interface RetainedTurnCleanupOptions {
45
- abortSignal?: AbortSignal;
46
- deadlineMs?: number;
47
- /** Keep cleanup within the accepted namespace. */
48
- writeNamespaceOverride?: string;
49
- /** Match the original profile and project binding, not just its write namespace. */
50
- scopeProfileWritePlan?: import("./namespaces/scope-profiles.js").ResolvedScopeProfilePlan | null;
51
- codingContext?: import("./types.js").CodingContext;
52
- }
53
-
54
49
  // Raised from 200 in #3303: live captures were being dropped at busy hours
55
50
  // while the single extraction worker drained a backlog.
56
51
  const FALLBACK_MAX_BUFFER_SESSIONS = 1000;
57
- const isAcceptedTurn = (t: { acceptedCapture?: boolean; writeNamespaceOverride?: string }): boolean =>
58
- t.acceptedCapture === true || t.writeNamespaceOverride !== undefined;
59
52
 
60
53
  /**
61
54
  * Upper bound on how long a debounced buffer save may be deferred under
@@ -76,15 +69,6 @@ const BUFFER_SAVE_MAX_DEFER_MULTIPLIER = 5;
76
69
  */
77
70
  const MAX_SET_TIMEOUT_MS = 2_147_483_647;
78
71
 
79
- interface AddTurnMutationResult {
80
- decision: TriggerDecision;
81
- signalLevel: SignalLevel;
82
- priorTurns: BufferTurn[];
83
- activeTurnsSnapshot: BufferTurn[];
84
- retainedTurnsSnapshot: BufferTurn[];
85
- turnCountInWindow: number;
86
- }
87
-
88
72
  export class SmartBuffer {
89
73
  private state: BufferState;
90
74
  private loaded = false;
@@ -182,61 +166,13 @@ export class SmartBuffer {
182
166
  };
183
167
  }
184
168
 
185
- private normalizeState(state: BufferState): BufferState {
186
- const entries = Object.assign(
187
- Object.create(null),
188
- state.entries ?? {},
189
- ) as NonNullable<BufferState["entries"]>;
190
- if (!entries.default) {
191
- entries.default = {
192
- turns: Array.isArray(state.turns) ? [...state.turns] : [],
193
- lastExtractionAt: state.lastExtractionAt ?? null,
194
- extractionCount:
195
- typeof state.extractionCount === "number" ? state.extractionCount : 0,
196
- };
197
- }
198
- return {
199
- turns: entries.default.turns,
200
- lastExtractionAt: entries.default.lastExtractionAt,
201
- extractionCount: entries.default.extractionCount,
202
- entries,
203
- };
204
- }
205
-
206
- private entryActivityAt(entry: BufferEntryState): number {
207
- const lastTurnAt = entry.turns.reduce((latest, turn) => {
208
- const parsed = Date.parse(turn.timestamp);
209
- return Number.isFinite(parsed) ? Math.max(latest, parsed) : latest;
210
- }, -1);
211
- const lastExtractionAt =
212
- typeof entry.lastExtractionAt === "string"
213
- ? Date.parse(entry.lastExtractionAt)
214
- : Number.NaN;
215
- return Math.max(
216
- lastTurnAt,
217
- Number.isFinite(lastExtractionAt) ? lastExtractionAt : -1,
218
- );
219
- }
220
-
221
169
  /** Evict oldest entries past this.maxSessionEntries and queue their session files for deletion. */
222
170
  private pruneEntries(retainKeys: string[], maxCount = this.maxSessionEntries): void {
223
171
  const entries = this.state.entries;
224
172
  if (!entries) return;
225
- const evicted = selectEvictedBufferKeys(entries, retainKeys, maxCount, {
226
- activityAt: (entry) => this.entryActivityAt(entry),
227
- holdsAcceptedCapture: (entry) => this.entryHoldsAcceptedCapture(entry),
228
- });
173
+ const evicted = selectEvictedBufferKeys(entries, retainKeys, maxCount, EVICTION_HOOKS);
229
174
  for (const key of evicted) delete entries[key];
230
175
  this.persistence.markEvicted(evicted);
231
- if (evicted.length > 0) {
232
- // Loud degradation (extraction hot-loop hardening, Step 7): buffer
233
- // pressure during a provider outage is bounded and expected, but must
234
- // be observable, not silent.
235
- log.warn(
236
- `buffer: pruned ${evicted.length} oldest session entr${evicted.length === 1 ? "y" : "ies"} ` +
237
- `past the session-entry cap (${this.maxSessionEntries}); buffered turn data for those sessions was dropped`,
238
- );
239
- }
240
176
  }
241
177
 
242
178
  private async loadUnlocked(): Promise<void> {
@@ -244,7 +180,7 @@ export class SmartBuffer {
244
180
  if (!this.loadPromise) {
245
181
  this.loadPromise = this.storage.loadBuffer()
246
182
  .then((state) => {
247
- this.state = this.normalizeState(state);
183
+ this.state = normalizeBufferState(state);
248
184
  this.loaded = true;
249
185
  })
250
186
  .finally(() => {
@@ -517,24 +453,80 @@ export class SmartBuffer {
517
453
  if (mutation === null) return null;
518
454
  return this.completeAddTurnOutcome(bufferKey, turn, mutation);
519
455
  }
456
+ /**
457
+ * Admit a multi-session durable capture as one capacity transaction.
458
+ * Preflight every new key under the mutation lock before persisting any
459
+ * turn; refusal records nothing and drops nothing.
460
+ */
461
+ async addTurnsBounded(
462
+ batches: readonly { bufferKey: string; turns: readonly BufferTurn[] }[],
463
+ ): Promise<Array<{ bufferKey: string; turn: BufferTurn; outcome: AddTurnOutcome }> | null> {
464
+ const pending = await this.enqueueMutation(async () => {
465
+ await this.loadUnlocked();
466
+ const additions: Array<{ bufferKey: string; turn: BufferTurn }> = [];
467
+ for (const batch of batches) {
468
+ for (const turn of batch.turns) {
469
+ additions.push({ bufferKey: batch.bufferKey, turn });
470
+ }
471
+ }
472
+
473
+ const entries = this.state.entries ?? {};
474
+ const newKeys = [...new Set(
475
+ additions
476
+ .map(({ bufferKey }) => bufferKey)
477
+ .filter((bufferKey) => this.peekEntry(bufferKey) === null),
478
+ )];
479
+ const count = Object.keys(entries).length;
480
+ const requiredEvictions = Math.max(0, count + newKeys.length - this.maxSessionEntries);
481
+ if (requiredEvictions > 0) {
482
+ const target = Math.max(0, this.maxSessionEntries - newKeys.length);
483
+ const evicted = selectEvictedBufferKeys(entries, newKeys, target, EVICTION_HOOKS);
484
+ if (newKeys.length > 0 && evicted.length < requiredEvictions) return null;
485
+ for (const key of evicted) delete entries[key];
486
+ this.persistence.markEvicted(evicted);
487
+ }
488
+
489
+ const recorded: Array<{ bufferKey: string; turn: BufferTurn; mutation: AddTurnMutationResult }> = [];
490
+ for (const { bufferKey, turn } of additions) {
491
+ recorded.push({ bufferKey, turn, mutation: await this.recordTurnUnlocked(bufferKey, turn) });
492
+ }
493
+ return recorded;
494
+ });
495
+ if (pending === null) return null;
496
+
497
+ const outcomes: Array<{ bufferKey: string; turn: BufferTurn; outcome: AddTurnOutcome }> = [];
498
+ for (const item of pending) {
499
+ outcomes.push({
500
+ bufferKey: item.bufferKey,
501
+ turn: item.turn,
502
+ outcome: await this.completeAddTurnOutcome(item.bufferKey, item.turn, item.mutation),
503
+ });
504
+ }
505
+ return outcomes;
506
+ }
520
507
 
521
- /** Free one slot for an accepted capture via the native eviction policy (COS-244c). */
508
+ /** Free one slot only when an empty entry can be reclaimed. */
522
509
  private makeRoomForEntryUnlocked(): void {
523
510
  const entries = this.state.entries ?? {};
524
511
  const target = Math.max(0, this.maxSessionEntries - 1);
525
- const pinned = Object.keys(entries).filter((k) => k === "default" || this.entryHoldsAcceptedCapture(entries[k]));
526
- if (pinned.length <= target) this.pruneEntries([], target); // evict only if it frees a slot
527
- }
528
-
529
- private entryHoldsAcceptedCapture(entry: BufferEntryState | undefined): boolean {
530
- return (entry?.turns ?? []).some(isAcceptedTurn) || (entry?.retainedTurns ?? []).some(isAcceptedTurn);
512
+ const pinned = Object.keys(entries).filter((k) => k === "default" || entryHoldsAcceptedCapture(entries[k]));
513
+ if (pinned.length <= target) this.pruneEntries([], target);
531
514
  }
532
515
 
533
516
  async addTurnWithOutcome(
534
517
  bufferKey: string,
535
518
  turn: BufferTurn,
536
519
  ): Promise<AddTurnOutcome> {
537
- const mutation = await this.enqueueMutation(() => this.recordTurnUnlocked(bufferKey, turn));
520
+ const mutation = await this.enqueueMutation(async () => {
521
+ await this.loadUnlocked();
522
+ if (
523
+ this.peekEntry(bufferKey) === null &&
524
+ !canAdmitNewEntryUnlocked(this.state.entries ?? {}, bufferKey, this.maxSessionEntries, EVICTION_HOOKS)
525
+ ) {
526
+ throw new BufferAdmissionRefusedError(bufferKey, this.maxSessionEntries);
527
+ }
528
+ return this.recordTurnUnlocked(bufferKey, turn);
529
+ });
538
530
  return this.completeAddTurnOutcome(bufferKey, turn, mutation);
539
531
  }
540
532
 
@@ -0,0 +1,109 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { access, realpath, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { ZodError } from "zod";
5
+ import { EngramAccessInputError } from "./access-errors.js";
6
+ import { defaultCapsulesDir } from "./capsule-cli.js";
7
+
8
+ /**
9
+ * Import-path gate for remote capsule imports (issue #3225 C1).
10
+ *
11
+ * `EngramAccessService.capsuleImport` serves the HTTP route and the
12
+ * `capsule_import` MCP operation, where the caller supplies `archivePath`.
13
+ * The destination namespace is authorized, but the source archive previously
14
+ * was not: any path the server process could read was accepted. This module
15
+ * confines the source to the namespace capsule store, `defaultCapsulesDir`
16
+ * of the destination storage dir, which is the same store `capsule export`
17
+ * writes and `capsule_list` lists.
18
+ *
19
+ * The CLI import path does not go through this gate; it is the explicit
20
+ * operator authority and keeps unrestricted local-path imports.
21
+ */
22
+
23
+ /** True when the caught error carries the given errno-style code. */
24
+ function hasFsErrorCode(cause: unknown, code: string): boolean {
25
+ return cause instanceof Object && "code" in cause && cause.code === code;
26
+ }
27
+
28
+ /** True when `cause` is a path error the gate reports as an input error instead of a server fault. */
29
+ function isInputFsError(cause: unknown): boolean {
30
+ return (
31
+ hasFsErrorCode(cause, "ENOENT") ||
32
+ hasFsErrorCode(cause, "ENOTDIR") ||
33
+ hasFsErrorCode(cause, "EACCES") ||
34
+ hasFsErrorCode(cause, "EPERM") ||
35
+ hasFsErrorCode(cause, "ELOOP") ||
36
+ hasFsErrorCode(cause, "ENAMETOOLONG") ||
37
+ hasFsErrorCode(cause, "ERR_INVALID_ARG_VALUE")
38
+ );
39
+ }
40
+
41
+ /** Matches zod-style `Z_` error codes carried on error objects. */
42
+ const ZOD_STYLE_CODE = /^Z_/;
43
+
44
+ /** True when the caught error carries a zod-style `Z_` code. */
45
+ function hasZodStyleErrorCode(cause: unknown): boolean {
46
+ return cause instanceof Object && "code" in cause && ZOD_STYLE_CODE.test(String(cause.code));
47
+ }
48
+
49
+ /** True when the importer failed on archive input (shape, gzip, checksum) rather than backend errors. */
50
+ export function isCapsuleImportArchiveInputError(cause: unknown, message: string): boolean {
51
+ if (cause instanceof ZodError) return true;
52
+ if (hasZodStyleErrorCode(cause)) return true;
53
+ return (
54
+ message.startsWith("importCapsule: archive") ||
55
+ message.startsWith("importCapsule: bundle") ||
56
+ message.startsWith("importCapsule: manifest") ||
57
+ message.startsWith("importCapsule: record") ||
58
+ /incorrect header check|invalid stored block lengths|not in gzip format|unexpected end of file/i.test(message)
59
+ );
60
+ }
61
+
62
+ /**
63
+ * One message for every refusal before confinement is proven. A caller that
64
+ * can name any server path must not learn from the error whether that path
65
+ * exists, is a directory, or is unreadable.
66
+ */
67
+ const IMPORT_SOURCE_REFUSED =
68
+ "capsule import failed: archivePath must be an existing file inside the namespace capsule store";
69
+
70
+ /** Resolve a path to its real path; a missing, looping, or unreadable path resolves to null. */
71
+ async function realpathOrNull(target: string): Promise<string | null> {
72
+ try {
73
+ return await realpath(target);
74
+ } catch (cause) {
75
+ if (!isInputFsError(cause)) throw cause;
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /** True when `target` is a strict descendant of `directory` (both already resolved). */
81
+ function isInsideDirectory(directory: string, target: string): boolean {
82
+ const rel = path.relative(directory, target);
83
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
84
+ }
85
+
86
+ /**
87
+ * Validate a remote import source and confine it to the namespace capsule
88
+ * store. Both real paths are resolved first, so `..` segments and store
89
+ * symlinks that point outside are refused. Confinement is checked before the
90
+ * file is inspected in any other way, and every failure before it proves
91
+ * confinement raises the same message.
92
+ */
93
+ export async function validateCapsuleImportArchive(archivePath: string, storageDir: string): Promise<void> {
94
+ const storeReal = await realpathOrNull(defaultCapsulesDir(storageDir));
95
+ const archiveReal = await realpathOrNull(archivePath);
96
+ if (storeReal === null || archiveReal === null || !isInsideDirectory(storeReal, archiveReal)) {
97
+ throw new EngramAccessInputError(IMPORT_SOURCE_REFUSED);
98
+ }
99
+ // The path now names a store entry, so the checks below only describe the store.
100
+ if (!(await stat(archiveReal)).isFile()) {
101
+ throw new EngramAccessInputError("capsule import failed: archivePath must point to a file");
102
+ }
103
+ try {
104
+ await access(archiveReal, fsConstants.R_OK);
105
+ } catch (cause) {
106
+ if (!isInputFsError(cause)) throw cause;
107
+ throw new EngramAccessInputError("capsule import failed: archive is not readable");
108
+ }
109
+ }
@@ -593,3 +593,79 @@ test("a quote naming rewritten text is located after temporal anchoring", () =>
593
593
  );
594
594
  assert.equal(grounded.facts.length, 1);
595
595
  });
596
+
597
+ const TURN_ONE =
598
+ "The river gauges read high all week near the mill.\n\nAlso the culvert repair is scheduled for next Tuesday at the plant.";
599
+ const TURN_TWO = "A different conversation about the warehouse roof leaks and the insurance claim.";
600
+ const TURNED_SOURCE = `${TURN_ONE}\n\n${TURN_TWO}`;
601
+
602
+ test("a quote crossing a blank line inside one turn is anchored when turn boundaries are given", () => {
603
+ const quote = "near the mill. Also the culvert repair is scheduled for next Tuesday at the plant";
604
+ const anchors = quoteAnchors(quote, TURNED_SOURCE, undefined, [TURN_ONE, TURN_TWO]);
605
+ assert.equal(anchors.length, 1);
606
+ assert.equal(anchors[0]!.quote, quote);
607
+ });
608
+
609
+ test("a quote spanning two turns still yields no anchor even with turn boundaries given", () => {
610
+ const stitched = "scheduled for next Tuesday at the plant.\n\nA different conversation about the warehouse";
611
+ assert.deepEqual(quoteAnchors(stitched, TURNED_SOURCE, undefined, [TURN_ONE, TURN_TWO]), []);
612
+ });
613
+
614
+ test("the anchor window reaches past a blank line to its turn's end, never into the next turn", () => {
615
+ const [anchor] = quoteAnchors(
616
+ "The river gauges read high all week near the mill.",
617
+ TURNED_SOURCE,
618
+ undefined,
619
+ [TURN_ONE, TURN_TWO],
620
+ );
621
+ assert.ok(anchor !== undefined);
622
+ assert.ok(anchor.window.includes("culvert repair is scheduled for next Tuesday at the plant"));
623
+ assert.ok(!anchor.window.includes("warehouse"));
624
+ });
625
+
626
+ test("without turn boundaries a blank line still rejects the quote and cuts the window", () => {
627
+ const quote = "near the mill. Also the culvert repair is scheduled for next Tuesday at the plant";
628
+ assert.deepEqual(quoteAnchors(quote, TURNED_SOURCE), []);
629
+ const [anchor] = quoteAnchors("The river gauges read high all week near the mill.", TURNED_SOURCE);
630
+ assert.ok(anchor !== undefined);
631
+ assert.ok(!anchor.window.includes("culvert repair"));
632
+ });
633
+
634
+ test("turn texts that do not tile the source fall back to blank-line behavior", () => {
635
+ const quote = "near the mill. Also the culvert repair is scheduled for next Tuesday at the plant";
636
+ assert.deepEqual(quoteAnchors(quote, TURNED_SOURCE, undefined, ["a turn that is not in the source"]), []);
637
+ });
638
+
639
+ const LONG_TAIL = Array.from({ length: 60 }, (_, index) => `word${index} filler`).join(" ");
640
+ const LONG_TURN = `The river gauges read high all week near the mill. ${LONG_TAIL}`;
641
+ const LONG_SOURCE = `${LONG_TURN}\n\n${TURN_TWO}`;
642
+
643
+ test("the window cut by the turn length ends on a whole word, matching the no-turns window", () => {
644
+ const quote = "The river gauges read high all week near the mill.";
645
+ const [bounded] = quoteAnchors(quote, LONG_SOURCE, undefined, [LONG_TURN, TURN_TWO]);
646
+ const [plain] = quoteAnchors(quote, LONG_TURN);
647
+ assert.ok(bounded !== undefined && plain !== undefined);
648
+ assert.equal(bounded.window, plain.window);
649
+ });
650
+
651
+ test("the window of a quote in a non-last turn does not end with a separator newline", () => {
652
+ const [anchor] = quoteAnchors(
653
+ "The river gauges read high all week near the mill.",
654
+ TURNED_SOURCE,
655
+ undefined,
656
+ [TURN_ONE, TURN_TWO],
657
+ );
658
+ assert.ok(anchor !== undefined);
659
+ assert.ok(!anchor.window.endsWith("\n"));
660
+ });
661
+
662
+ test("a quote ending exactly at the last character of a non-last turn still anchors", () => {
663
+ const anchors = quoteAnchors(
664
+ "Also the culvert repair is scheduled for next Tuesday at the plant.",
665
+ TURNED_SOURCE,
666
+ undefined,
667
+ [TURN_ONE, TURN_TWO],
668
+ );
669
+ assert.equal(anchors.length, 1);
670
+ assert.ok(anchors[0]!.window.endsWith("at the plant."));
671
+ });
@@ -67,7 +67,18 @@ function normalizeWhitespaceWithMap(source: string): { text: string; originalInd
67
67
  return { text: chars.join(""), originalIndex };
68
68
  }
69
69
 
70
- function findQuoteAnchors(quote: string, source: string): QuoteAnchor[] {
70
+ function turnStartOffsets(source: string, turns: readonly string[]): readonly number[] | undefined {
71
+ const offsets: number[] = [];
72
+ let at = 0;
73
+ for (const turn of turns) {
74
+ if (!source.startsWith(turn, at)) return undefined;
75
+ offsets.push(at);
76
+ at += turn.length + TURN_SEPARATOR.length;
77
+ }
78
+ return at - TURN_SEPARATOR.length === source.length ? offsets : undefined;
79
+ }
80
+
81
+ function findQuoteAnchors(quote: string, source: string, bounds?: readonly number[]): QuoteAnchor[] {
71
82
  const needle = quote.replace(/\s+/gu, " ").trim();
72
83
  if (needle.length < QUOTE_ANCHOR_MIN_CHARS) return [];
73
84
  const normalized = normalizeWhitespaceWithMap(source);
@@ -79,17 +90,36 @@ function findQuoteAnchors(quote: string, source: string): QuoteAnchor[] {
79
90
  from = at + 1;
80
91
  const start = normalized.originalIndex[at]!;
81
92
  const end = normalized.originalIndex[at + needle.length - 1]! + 1;
82
- if (source.slice(start, end).includes(TURN_SEPARATOR)) continue;
83
-
84
- let before = source.slice(Math.max(0, start - QUOTE_ANCHOR_CONTEXT_CHARS), start);
85
- const turnStart = before.lastIndexOf(TURN_SEPARATOR);
86
- if (turnStart >= 0) before = before.slice(turnStart + TURN_SEPARATOR.length);
87
- else if (start > QUOTE_ANCHOR_CONTEXT_CHARS) before = before.replace(/^\S+/u, "");
88
93
 
89
- let after = source.slice(end, end + QUOTE_ANCHOR_CONTEXT_CHARS);
90
- const turnEnd = after.indexOf(TURN_SEPARATOR);
91
- if (turnEnd >= 0) after = after.slice(0, turnEnd);
92
- else if (source.length - end > QUOTE_ANCHOR_CONTEXT_CHARS) after = after.replace(/\S+$/u, "");
94
+ let before: string;
95
+ let after: string;
96
+ if (bounds === undefined) {
97
+ if (source.slice(start, end).includes(TURN_SEPARATOR)) continue;
98
+ before = source.slice(Math.max(0, start - QUOTE_ANCHOR_CONTEXT_CHARS), start);
99
+ const sepBefore = before.lastIndexOf(TURN_SEPARATOR);
100
+ if (sepBefore >= 0) before = before.slice(sepBefore + TURN_SEPARATOR.length);
101
+ else if (start > QUOTE_ANCHOR_CONTEXT_CHARS) before = before.replace(/^\S+/u, "");
102
+ after = source.slice(end, end + QUOTE_ANCHOR_CONTEXT_CHARS);
103
+ const sepAfter = after.indexOf(TURN_SEPARATOR);
104
+ if (sepAfter >= 0) after = after.slice(0, sepAfter);
105
+ else if (source.length - end > QUOTE_ANCHOR_CONTEXT_CHARS) after = after.replace(/\S+$/u, "");
106
+ } else {
107
+ let turnStart = 0;
108
+ let turnEnd = source.length;
109
+ for (const bound of bounds) {
110
+ if (bound <= start) turnStart = bound;
111
+ else {
112
+ turnEnd = bound - TURN_SEPARATOR.length;
113
+ break;
114
+ }
115
+ }
116
+ if (turnEnd < end) continue;
117
+ before = source.slice(Math.max(turnStart, start - QUOTE_ANCHOR_CONTEXT_CHARS), start);
118
+ if (start - QUOTE_ANCHOR_CONTEXT_CHARS > turnStart) before = before.replace(/^\S+/u, "");
119
+ const contextEnd = Math.min(turnEnd, end + QUOTE_ANCHOR_CONTEXT_CHARS);
120
+ after = source.slice(end, contextEnd);
121
+ if (end + QUOTE_ANCHOR_CONTEXT_CHARS < turnEnd) after = after.replace(/\S+$/u, "");
122
+ }
93
123
 
94
124
  anchors.push({ quote: needle, window: `${before}${needle.replace(/;/gu, ",")}${after}` });
95
125
  }
@@ -109,16 +139,22 @@ function findQuoteAnchors(quote: string, source: string): QuoteAnchor[] {
109
139
  * `source` may have been rewritten before grounding (temporal anchoring,
110
140
  * pronoun resolution) while the model quoted the original words. When the raw
111
141
  * quote is not found, `transformQuote` applies the same rewrite to the quote.
142
+ *
143
+ * `turns` are the texts `source` was joined from; when they tile `source`
144
+ * exactly, quotes and windows cut at real turn boundaries instead of any
145
+ * blank line. Without them, every `TURN_SEPARATOR` run is a boundary.
112
146
  */
113
147
  export function quoteAnchors(
114
148
  quote: string | undefined,
115
149
  source: string,
116
150
  transformQuote?: (quote: string) => string,
151
+ turns?: readonly string[],
117
152
  ): QuoteAnchor[] {
118
153
  if (typeof quote !== "string") return [];
119
- const exact = findQuoteAnchors(quote, source);
154
+ const bounds = turns === undefined ? undefined : turnStartOffsets(source, turns);
155
+ const exact = findQuoteAnchors(quote, source, bounds);
120
156
  if (exact.length > 0 || transformQuote === undefined) return exact;
121
- return findQuoteAnchors(transformQuote(quote), source);
157
+ return findQuoteAnchors(transformQuote(quote), source, bounds);
122
158
  }
123
159
 
124
160
  export function tokenPool(tokens: ReadonlySet<string>): TokenPool {
@@ -43,6 +43,8 @@ export interface ExtractionSourceGroundingOptions {
43
43
  spanMode?: "off" | "shadow" | "on";
44
44
  /** Rewrite applied to the source before this stage, so a verbatim quote can still be located in it. */
45
45
  quoteTransform?: (quote: string) => string;
46
+ /** Turn texts the sources were joined from, so quote anchors cut at real turn boundaries. */
47
+ turns?: readonly string[];
46
48
  }
47
49
 
48
50
  export function applyExtractionSourceGrounding(
@@ -68,6 +70,7 @@ export function applyExtractionSourceGrounding(
68
70
  ? undefined
69
71
  : anchorSource(roleAssertionSources.identity),
70
72
  };
73
+ const anchoredTurns = options.turns === undefined ? undefined : options.turns.map((turn) => anchorSource(turn));
71
74
  return filterExtractionResultBySource(
72
75
  result,
73
76
  anchorSource(sourceText),
@@ -78,6 +81,7 @@ export function applyExtractionSourceGrounding(
78
81
  options.anchorTemporalExpressions
79
82
  ? (quote) => anchorSource(options.quoteTransform?.(quote) ?? quote)
80
83
  : undefined,
84
+ anchoredTurns,
81
85
  );
82
86
  }
83
87
 
@@ -1117,6 +1121,7 @@ export function filterExtractionResultBySource(
1117
1121
  eventTimeNormalizer?: (eventTime: string) => string,
1118
1122
  spanMode: "off" | "shadow" | "on" = "off",
1119
1123
  quoteTransform?: (quote: string) => string,
1124
+ turns?: readonly string[],
1120
1125
  ): ExtractionResult {
1121
1126
  if (source.trim().length === 0) {
1122
1127
  return {
@@ -1136,7 +1141,7 @@ export function filterExtractionResultBySource(
1136
1141
  result.facts.some((fact) => typeof fact.quote === "string") ? tokenize(quoteSource) : new Set(),
1137
1142
  );
1138
1143
  const facts = result.facts.flatMap((fact) => {
1139
- const anchors = quoteAnchors(fact.quote, quoteSource, quoteTransform);
1144
+ const anchors = quoteAnchors(fact.quote, quoteSource, quoteTransform, turns);
1140
1145
  const grounded = filterGroundedFact(
1141
1146
  fact,
1142
1147
  source,
@@ -101,6 +101,7 @@ export function filterExtractionResultBySource(
101
101
  eventTimeNormalizer?: (eventTime: string) => string,
102
102
  spanMode: "off" | "shadow" | "on" = "off",
103
103
  quoteTransform?: (quote: string) => string,
104
+ turns?: readonly string[],
104
105
  ): ExtractionResult {
105
106
  return filterGroundingRules(
106
107
  result,
@@ -110,5 +111,6 @@ export function filterExtractionResultBySource(
110
111
  eventTimeNormalizer,
111
112
  spanMode,
112
113
  quoteTransform,
114
+ turns,
113
115
  );
114
116
  }
package/src/extraction.ts CHANGED
@@ -1122,10 +1122,8 @@ export class ExtractionEngine {
1122
1122
  const groundingSource = boundedTurns
1123
1123
  .map((turn) => turn.content)
1124
1124
  .join("\n\n");
1125
- const assertionSource = boundedTurns
1126
- .filter((turn) => turn.extractionContextOnly !== true)
1127
- .map((turn) => turn.content)
1128
- .join("\n\n");
1125
+ const assertionTurns = boundedTurns.filter((turn) => turn.extractionContextOnly !== true);
1126
+ const assertionSource = assertionTurns.map((turn) => turn.content).join("\n\n");
1129
1127
  const roleAssertionSources: ExtractionGroundingRoleSources = {
1130
1128
  profile: boundedTurns
1131
1129
  .filter((turn) => turn.role === "user" && turn.extractionContextOnly !== true)
@@ -1171,6 +1169,7 @@ export class ExtractionEngine {
1171
1169
  sourceConnector,
1172
1170
  ambientCapture,
1173
1171
  scopeClassificationEnabled: lifecycleCaps.extractionScopeClassification,
1172
+ turns: assertionTurns.map((turn) => turn.content),
1174
1173
  };
1175
1174
 
1176
1175
  const traceId = crypto.randomUUID();