@remnic/core 10.0.69 → 10.0.71

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 (412) hide show
  1. package/dist/access-admin-ops-surface.js +82 -79
  2. package/dist/access-authorization-probe.js +83 -80
  3. package/dist/access-boundary.js +82 -79
  4. package/dist/access-cli.js +196 -193
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +82 -79
  7. package/dist/access-http-briefing.js +83 -80
  8. package/dist/access-http-query.js +84 -81
  9. package/dist/access-http.js +106 -103
  10. package/dist/access-identity-continuity-surface.js +82 -79
  11. package/dist/access-lcm-surface.js +82 -79
  12. package/dist/access-mcp.js +95 -92
  13. package/dist/access-namespace-preflight.js +82 -79
  14. package/dist/access-observe-lcm-archive.d.ts +5 -0
  15. package/dist/access-observe-lcm-archive.d.ts.map +1 -0
  16. package/dist/access-observe-lcm-archive.js +9 -0
  17. package/dist/access-observe-objective-state.d.ts +8 -0
  18. package/dist/access-observe-objective-state.d.ts.map +1 -0
  19. package/dist/access-observe-objective-state.js +17 -0
  20. package/dist/access-observe-stage-timing.d.ts +11 -0
  21. package/dist/access-observe-stage-timing.d.ts.map +1 -0
  22. package/dist/access-observe-stage-timing.js +9 -0
  23. package/dist/access-observe-stage-timing.js.map +1 -0
  24. package/dist/access-observe-write-category.js +13 -13
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +82 -79
  27. package/dist/access-offline-manifest.js +8 -8
  28. package/dist/access-operations-batch.js +85 -82
  29. package/dist/access-operations.js +92 -89
  30. package/dist/access-recall-concurrency.js +82 -79
  31. package/dist/access-recall-response.js +82 -79
  32. package/dist/access-recall-surface.js +82 -79
  33. package/dist/access-schema.js +12 -12
  34. package/dist/access-service.d.ts +1 -0
  35. package/dist/access-service.d.ts.map +1 -1
  36. package/dist/access-service.js +82 -79
  37. package/dist/access-surface-catalog.js +84 -81
  38. package/dist/access-surface-catalog.js.map +1 -1
  39. package/dist/active-recall.js +26 -26
  40. package/dist/adapters/index.js +4 -4
  41. package/dist/adapters/registry.js +2 -2
  42. package/dist/briefing.js +17 -17
  43. package/dist/buffer-turn-helpers.d.ts +10 -1
  44. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  45. package/dist/buffer-turn-helpers.js +3 -1
  46. package/dist/buffer.d.ts +4 -0
  47. package/dist/buffer.d.ts.map +1 -1
  48. package/dist/buffer.js +9 -9
  49. package/dist/calibration.js +4 -4
  50. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  51. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  52. package/dist/causal-behavior.js +2 -2
  53. package/dist/causal-chain.js +2 -2
  54. package/dist/causal-consolidation.js +22 -22
  55. package/dist/causal-retrieval.js +2 -2
  56. package/dist/causal-trajectory-graph.js +3 -3
  57. package/dist/causal-trajectory.js +1 -1
  58. package/dist/{chunk-6WKWL53V.js → chunk-23BA3USG.js} +128 -122
  59. package/dist/chunk-23BA3USG.js.map +1 -0
  60. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  61. package/dist/{chunk-CI3MAAIY.js → chunk-2R44BBPK.js} +2 -2
  62. package/dist/{chunk-QDZWQBLU.js → chunk-2R7OCBRR.js} +2 -2
  63. package/dist/{chunk-B3MWELZ3.js → chunk-2WPWEVAH.js} +2 -2
  64. package/dist/{chunk-PLCYKAWH.js → chunk-3ICAWZUU.js} +4 -4
  65. package/dist/{chunk-R4ZHF4K2.js → chunk-43FLJLT2.js} +4 -4
  66. package/dist/{chunk-PMDOGXA2.js → chunk-4PMFSYH7.js} +2 -2
  67. package/dist/{chunk-HXFIILGF.js → chunk-4RASUAN3.js} +4 -4
  68. package/dist/{chunk-EI6M5NVK.js → chunk-5JBVPJCT.js} +2 -2
  69. package/dist/{chunk-4NV7O76Q.js → chunk-5ORJHH43.js} +2 -2
  70. package/dist/{chunk-74AY3S7U.js → chunk-5RW247F5.js} +10 -10
  71. package/dist/{chunk-4XINO77L.js → chunk-66JOLMWS.js} +2 -2
  72. package/dist/{chunk-AYJQ644F.js → chunk-6KBCUIZP.js} +2 -2
  73. package/dist/{chunk-GKL6QYZO.js → chunk-6L63IN3M.js} +1 -1
  74. package/dist/{chunk-OPFVXM2D.js → chunk-7P5VBRBS.js} +2 -2
  75. package/dist/{chunk-4SOWMA55.js → chunk-7WFY2L5J.js} +3 -3
  76. package/dist/{chunk-GJ4HU6LK.js → chunk-AGRMXXVA.js} +2 -2
  77. package/dist/{chunk-KOLJJN5G.js → chunk-AQNHEDXP.js} +11 -4
  78. package/dist/chunk-AQNHEDXP.js.map +1 -0
  79. package/dist/{chunk-IVUVQPTQ.js → chunk-AZNE2H6D.js} +2 -2
  80. package/dist/{chunk-HPB5TF55.js → chunk-BCBY5GYZ.js} +3 -3
  81. package/dist/{chunk-M4IEZGCK.js → chunk-BFBS7OFQ.js} +7 -7
  82. package/dist/{chunk-4PG5R6BJ.js → chunk-BNU5TQLR.js} +2 -2
  83. package/dist/{chunk-TVE7W67N.js → chunk-BTU77RB5.js} +2 -2
  84. package/dist/{chunk-H6VJSITJ.js → chunk-CIL4GHMD.js} +3 -3
  85. package/dist/{chunk-KZZ5EZ7O.js → chunk-DC3DWF2M.js} +2 -2
  86. package/dist/{chunk-R2BIVU2Z.js → chunk-DE4U5PTV.js} +50 -46
  87. package/dist/chunk-DE4U5PTV.js.map +1 -0
  88. package/dist/{chunk-DDBB55L2.js → chunk-DSSTOV5M.js} +534 -361
  89. package/dist/chunk-DSSTOV5M.js.map +1 -0
  90. package/dist/{chunk-4MPAKYZU.js → chunk-EGKDONTC.js} +12 -6
  91. package/dist/chunk-EGKDONTC.js.map +1 -0
  92. package/dist/{chunk-AUFPPBC3.js → chunk-ENPJX3BB.js} +3 -3
  93. package/dist/{chunk-CTH2CQCB.js → chunk-EQ6EMJRG.js} +2 -2
  94. package/dist/{chunk-63A2BU2B.js → chunk-F4ZHKDBW.js} +5 -5
  95. package/dist/{chunk-3LBDEGQA.js → chunk-FDUCIUQ5.js} +2 -2
  96. package/dist/{chunk-OCK4D5PC.js → chunk-FGGLVCHI.js} +2 -2
  97. package/dist/{chunk-XVZA6VXC.js → chunk-FZVROZKC.js} +4 -4
  98. package/dist/{chunk-3RHU5MD7.js → chunk-GXAZ4Q2S.js} +2 -2
  99. package/dist/{chunk-UVVFPCPX.js → chunk-H2X32F2K.js} +2 -2
  100. package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
  101. package/dist/{chunk-W7K5BP3M.js → chunk-HQPFACXO.js} +2 -2
  102. package/dist/{chunk-TECK5IVY.js → chunk-HZNURRDS.js} +2 -2
  103. package/dist/{chunk-U2CAGMUD.js → chunk-ICBUTKLL.js} +2 -2
  104. package/dist/{chunk-B4DXREWC.js → chunk-IQXQ3KWR.js} +2 -2
  105. package/dist/{chunk-AL3TCNSJ.js → chunk-J6GZ2254.js} +4 -4
  106. package/dist/{chunk-5MGKGEL7.js → chunk-J72ZY7NU.js} +2 -2
  107. package/dist/{chunk-OH55F76C.js → chunk-JEX2JL3U.js} +2 -2
  108. package/dist/{chunk-Y3XPSGNF.js → chunk-JGLZ46JS.js} +2 -2
  109. package/dist/{chunk-EXQJMFRF.js → chunk-JY4LXMZR.js} +2 -2
  110. package/dist/{chunk-6P7U3HWG.js → chunk-K36RMINQ.js} +3 -3
  111. package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
  112. package/dist/{chunk-LN4X2NDA.js → chunk-KY3KJUNN.js} +10 -10
  113. package/dist/chunk-L3E7MLMK.js +20 -0
  114. package/dist/chunk-L3E7MLMK.js.map +1 -0
  115. package/dist/{chunk-H5DIRCZP.js → chunk-LHMCZ6VW.js} +18 -18
  116. package/dist/{chunk-GGD2YDS6.js → chunk-LN7CWLAU.js} +2 -2
  117. package/dist/{chunk-GHHQLSSY.js → chunk-LYOCAUI6.js} +3 -3
  118. package/dist/{chunk-VA3GNAWQ.js → chunk-LZIOY7ZU.js} +4 -5
  119. package/dist/chunk-LZIOY7ZU.js.map +1 -0
  120. package/dist/{chunk-W26D2USW.js → chunk-MUH7RR6T.js} +6 -6
  121. package/dist/{chunk-IAGVM7EY.js → chunk-N2Y6COEM.js} +2 -2
  122. package/dist/{chunk-64JLBFCQ.js → chunk-N7NAGECH.js} +2 -2
  123. package/dist/{chunk-5Y6VA6X2.js → chunk-OGQGDOYK.js} +2 -2
  124. package/dist/{chunk-U7MIMS2Y.js → chunk-OUBEJWVI.js} +17 -1
  125. package/dist/chunk-OUBEJWVI.js.map +1 -0
  126. package/dist/{chunk-JLMOH5JA.js → chunk-OWZXDBV4.js} +2 -2
  127. package/dist/{chunk-ZORS5BG5.js → chunk-PJAMFIDE.js} +10 -10
  128. package/dist/chunk-QCFZDYYG.js +34 -0
  129. package/dist/chunk-QCFZDYYG.js.map +1 -0
  130. package/dist/{chunk-SXTVZ6JL.js → chunk-QIOQI7QZ.js} +2 -2
  131. package/dist/{chunk-MMCO4SRG.js → chunk-QNN4FYBD.js} +19 -11
  132. package/dist/chunk-QNN4FYBD.js.map +1 -0
  133. package/dist/{chunk-75DCOS6I.js → chunk-QUPTDKSX.js} +67 -67
  134. package/dist/{chunk-HH73M65H.js → chunk-QVZZE6VH.js} +3 -3
  135. package/dist/{chunk-XWE3EOWX.js → chunk-RZED7FAU.js} +2 -2
  136. package/dist/{chunk-IZBIDPMY.js → chunk-S37O4S6E.js} +2 -2
  137. package/dist/{chunk-NHWDTOBN.js → chunk-S73ZVU37.js} +21 -21
  138. package/dist/{chunk-YSATDEBY.js → chunk-SBRUTD5O.js} +5 -5
  139. package/dist/{chunk-WVP2I6C5.js → chunk-SDL22RRW.js} +50 -11
  140. package/dist/chunk-SDL22RRW.js.map +1 -0
  141. package/dist/{chunk-PRKNAIGZ.js → chunk-T6T2RMWW.js} +5 -5
  142. package/dist/{chunk-CIEEYVUB.js → chunk-TPX7XXY4.js} +2 -2
  143. package/dist/{chunk-L334PSLW.js → chunk-U2A56LLF.js} +8 -8
  144. package/dist/{chunk-P4TV3TQ2.js → chunk-U6IL4724.js} +5 -5
  145. package/dist/{chunk-5UM4H64E.js → chunk-UAOJVQXC.js} +2 -2
  146. package/dist/{chunk-MOIJWWNA.js → chunk-UE5JYPU6.js} +13 -13
  147. package/dist/{chunk-TGQW3AMK.js → chunk-UIFWZMO7.js} +23 -23
  148. package/dist/{chunk-UW6LBFYN.js → chunk-USX5YIKB.js} +6 -6
  149. package/dist/{chunk-QGFR73SN.js → chunk-VK5HKVQ7.js} +2 -2
  150. package/dist/{chunk-ZOBYTRBU.js → chunk-VNTQU6KB.js} +47 -2
  151. package/dist/chunk-VNTQU6KB.js.map +1 -0
  152. package/dist/{chunk-IHCXMYSE.js → chunk-VPEIWMUT.js} +2 -2
  153. package/dist/{chunk-O5ZINWW3.js → chunk-VSJ3ARFS.js} +2 -2
  154. package/dist/{chunk-RLVI2M5Q.js → chunk-W4V7P5HP.js} +2 -2
  155. package/dist/{chunk-ELHJK7LM.js → chunk-WBN66HPI.js} +6 -6
  156. package/dist/{chunk-BJLCV2P3.js → chunk-WGRAB3KB.js} +2 -2
  157. package/dist/{chunk-5KDDAQ6K.js → chunk-WN2DASTS.js} +10 -10
  158. package/dist/{chunk-IG3EZ3TF.js → chunk-WNQ3B3Q5.js} +2 -2
  159. package/dist/{chunk-V6SBA5O7.js → chunk-WYOHQJKC.js} +7 -7
  160. package/dist/chunk-WZU5AGO6.js +38 -0
  161. package/dist/chunk-WZU5AGO6.js.map +1 -0
  162. package/dist/{chunk-WHROVQ5Q.js → chunk-X4DHAXFO.js} +3 -3
  163. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  164. package/dist/{chunk-JLGBJ4AX.js → chunk-XPZM7GP6.js} +10 -10
  165. package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
  166. package/dist/{chunk-UYUFDTIX.js → chunk-YIXWY4OZ.js} +2 -2
  167. package/dist/{chunk-NCZGGGWI.js → chunk-YPYSC3TX.js} +12 -12
  168. package/dist/{chunk-AVGDYYMA.js → chunk-ZBET3W26.js} +3 -3
  169. package/dist/{chunk-Y7427SL7.js → chunk-ZG4GNPRE.js} +216 -50
  170. package/dist/chunk-ZG4GNPRE.js.map +1 -0
  171. package/dist/chunk-ZODKV27J.js +443 -0
  172. package/dist/chunk-ZODKV27J.js.map +1 -0
  173. package/dist/cli.js +133 -130
  174. package/dist/coding/export-okf-codegraph.js +16 -16
  175. package/dist/coding/pre-action-gate.js +2 -2
  176. package/dist/compounding/engine.js +18 -18
  177. package/dist/config.js +26 -26
  178. package/dist/connectors/codex-materialize-runner.js +19 -19
  179. package/dist/connectors/codex-materialize.js +2 -2
  180. package/dist/connectors/index.js +20 -20
  181. package/dist/consolidation-provenance-check.js +2 -2
  182. package/dist/convergence-refresh.js +20 -20
  183. package/dist/corpus-watermark.js +20 -20
  184. package/dist/deep-recall-http-glue.js +13 -13
  185. package/dist/deep-recall.js +2 -2
  186. package/dist/embedding-fallback.js +2 -2
  187. package/dist/entity-retrieval.js +20 -20
  188. package/dist/event-order-recall.js +2 -2
  189. package/dist/external-wiki-access.js +83 -80
  190. package/dist/extraction-span-materialize.js +2 -2
  191. package/dist/extraction-window-drain.d.ts.map +1 -1
  192. package/dist/extraction-window-drain.js +1 -1
  193. package/dist/extraction.js +32 -32
  194. package/dist/{graph-edge-decay-OEX5OGMI.js → graph-edge-decay-CXTNJ7IQ.js} +6 -6
  195. package/dist/graph-edge-decay-CXTNJ7IQ.js.map +1 -0
  196. package/dist/graph-jsonl.js +4 -4
  197. package/dist/graph-snapshot.js +4 -4
  198. package/dist/graph.js +3 -3
  199. package/dist/harmonic-retrieval.js +17 -17
  200. package/dist/index.js +281 -278
  201. package/dist/lcm/archive.d.ts +12 -2
  202. package/dist/lcm/archive.d.ts.map +1 -1
  203. package/dist/lcm/archive.js +2 -2
  204. package/dist/lcm/engine.d.ts +59 -8
  205. package/dist/lcm/engine.d.ts.map +1 -1
  206. package/dist/lcm/engine.js +6 -6
  207. package/dist/lcm/index.js +16 -16
  208. package/dist/lcm/queue.d.ts +7 -3
  209. package/dist/lcm/queue.d.ts.map +1 -1
  210. package/dist/lcm/queue.js +1 -1
  211. package/dist/lcm/summarizer.d.ts +8 -1
  212. package/dist/lcm/summarizer.d.ts.map +1 -1
  213. package/dist/lcm/summarizer.js +3 -3
  214. package/dist/local-llm.js +4 -4
  215. package/dist/location/index.js +19 -19
  216. package/dist/maintenance/memory-governance.js +18 -18
  217. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +17 -17
  218. package/dist/maintenance/rebuild-memory-projection-runner.js +20 -20
  219. package/dist/maintenance/rebuild-memory-projection.js +19 -19
  220. package/dist/memory-browse-operations.js +83 -80
  221. package/dist/memory-browse.js +6 -6
  222. package/dist/namespaces/migrate.js +18 -18
  223. package/dist/namespaces/search.js +7 -7
  224. package/dist/namespaces/storage.js +17 -17
  225. package/dist/offline-sync-changeset-generations.js +5 -5
  226. package/dist/offline-sync-embedding-generation.js +4 -4
  227. package/dist/offline-sync-snapshot-stream.js +7 -7
  228. package/dist/offline-sync.js +9 -9
  229. package/dist/operator-doctor-corpus.js +21 -21
  230. package/dist/operator-toolkit.js +41 -41
  231. package/dist/orchestration/backlog-scheduler.d.ts +151 -0
  232. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -0
  233. package/dist/orchestration/backlog-scheduler.js +891 -0
  234. package/dist/orchestration/backlog-scheduler.js.map +1 -0
  235. package/dist/orchestration/buffer-drain-runner.d.ts +31 -27
  236. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  237. package/dist/orchestration/buffer-drain-runner.js +202 -404
  238. package/dist/orchestration/buffer-drain-runner.js.map +1 -1
  239. package/dist/orchestration/extraction-drain.d.ts +50 -17
  240. package/dist/orchestration/extraction-drain.d.ts.map +1 -1
  241. package/dist/orchestration/extraction-queue-coordinator.d.ts +19 -1
  242. package/dist/orchestration/extraction-queue-coordinator.d.ts.map +1 -1
  243. package/dist/orchestration/extraction-run.d.ts +8 -0
  244. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  245. package/dist/orchestration/extraction-task-queueing.d.ts +44 -0
  246. package/dist/orchestration/extraction-task-queueing.d.ts.map +1 -0
  247. package/dist/orchestration/maintenance.js +33 -33
  248. package/dist/orchestration/turn-ingestion.d.ts +11 -5
  249. package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
  250. package/dist/orchestrator.d.ts +13 -0
  251. package/dist/orchestrator.d.ts.map +1 -1
  252. package/dist/orchestrator.js +196 -193
  253. package/dist/qmd.js +4 -4
  254. package/dist/recall-navigation-operations.js +83 -80
  255. package/dist/recall-navigation.js +82 -79
  256. package/dist/recall-state-view-admission.js +6 -6
  257. package/dist/recall-why-service.js +6 -6
  258. package/dist/resume-bundles.js +29 -29
  259. package/dist/search/factory.js +6 -6
  260. package/dist/search/index.js +12 -12
  261. package/dist/semantic-consolidation.js +25 -25
  262. package/dist/semantic-rule-promotion.js +17 -17
  263. package/dist/semantic-rule-verifier.js +17 -17
  264. package/dist/source-agent-qualifier.js +24 -24
  265. package/dist/standup/build.js +17 -17
  266. package/dist/storage/buffer-drain-claims.d.ts +15 -0
  267. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  268. package/dist/storage/buffer-drain-claims.js +2 -2
  269. package/dist/storage/buffer-session-files.js +2 -2
  270. package/dist/storage.js +16 -16
  271. package/dist/summarizer.js +6 -6
  272. package/dist/support-passport/index.js +85 -82
  273. package/dist/transfer/backup.js +2 -2
  274. package/dist/transfer/capsule-export.js +2 -2
  275. package/dist/transfer/capsule-import.js +2 -2
  276. package/dist/transfer/export-okf.js +17 -17
  277. package/dist/verified-recall.js +17 -17
  278. package/dist/who-knows-cli.js +18 -18
  279. package/dist/who-knows.js +17 -17
  280. package/package.json +12 -2
  281. package/src/access-observe-lcm-archive.ts +30 -0
  282. package/src/access-observe-lcm-fail-closed.test.ts +118 -0
  283. package/src/access-observe-objective-state.ts +40 -0
  284. package/src/access-observe-stage-timing.test.ts +246 -0
  285. package/src/access-observe-stage-timing.ts +58 -0
  286. package/src/access-observe-write-surface.ts +45 -45
  287. package/src/access-service.ts +1 -0
  288. package/src/backlog-scheduler.test.ts +806 -0
  289. package/src/buffer-drain-handover.test.ts +363 -18
  290. package/src/buffer-drain-inprocess.test.ts +355 -0
  291. package/src/buffer-drain-model-boundary.test.ts +1062 -0
  292. package/src/buffer-drop-extraction-chunk.test.ts +237 -22
  293. package/src/buffer-turn-helpers.ts +26 -1
  294. package/src/buffer.ts +8 -0
  295. package/src/extraction-window-drain.ts +7 -7
  296. package/src/lcm/archive.ts +59 -7
  297. package/src/lcm/engine.ts +256 -53
  298. package/src/lcm/queue.test.ts +32 -14
  299. package/src/lcm/queue.ts +24 -16
  300. package/src/lcm/summarizer.ts +15 -2
  301. package/src/lcm-durability.test.ts +856 -0
  302. package/src/lcm-engine.test.ts +12 -3
  303. package/src/orchestration/backlog-scheduler.ts +481 -0
  304. package/src/orchestration/buffer-drain-runner.ts +545 -180
  305. package/src/orchestration/extraction-drain.ts +287 -108
  306. package/src/orchestration/extraction-queue-coordinator.ts +51 -3
  307. package/src/orchestration/extraction-run.test.ts +41 -0
  308. package/src/orchestration/extraction-run.ts +24 -5
  309. package/src/orchestration/extraction-task-queueing.ts +87 -0
  310. package/src/orchestration/turn-ingestion.ts +46 -59
  311. package/src/orchestrator-extraction-queue.test.ts +46 -2
  312. package/src/orchestrator-flush.test.ts +4 -1
  313. package/src/orchestrator.ts +47 -21
  314. package/src/storage/buffer-drain-claims.ts +55 -0
  315. package/dist/chunk-4MPAKYZU.js.map +0 -1
  316. package/dist/chunk-6WKWL53V.js.map +0 -1
  317. package/dist/chunk-DDBB55L2.js.map +0 -1
  318. package/dist/chunk-KOLJJN5G.js.map +0 -1
  319. package/dist/chunk-MMCO4SRG.js.map +0 -1
  320. package/dist/chunk-R2BIVU2Z.js.map +0 -1
  321. package/dist/chunk-U7MIMS2Y.js.map +0 -1
  322. package/dist/chunk-VA3GNAWQ.js.map +0 -1
  323. package/dist/chunk-WVP2I6C5.js.map +0 -1
  324. package/dist/chunk-Y7427SL7.js.map +0 -1
  325. package/dist/chunk-ZOBYTRBU.js.map +0 -1
  326. /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-observe-lcm-archive.js.map} +0 -0
  327. /package/dist/{graph-edge-decay-OEX5OGMI.js.map → access-observe-objective-state.js.map} +0 -0
  328. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  329. /package/dist/{chunk-CI3MAAIY.js.map → chunk-2R44BBPK.js.map} +0 -0
  330. /package/dist/{chunk-QDZWQBLU.js.map → chunk-2R7OCBRR.js.map} +0 -0
  331. /package/dist/{chunk-B3MWELZ3.js.map → chunk-2WPWEVAH.js.map} +0 -0
  332. /package/dist/{chunk-PLCYKAWH.js.map → chunk-3ICAWZUU.js.map} +0 -0
  333. /package/dist/{chunk-R4ZHF4K2.js.map → chunk-43FLJLT2.js.map} +0 -0
  334. /package/dist/{chunk-PMDOGXA2.js.map → chunk-4PMFSYH7.js.map} +0 -0
  335. /package/dist/{chunk-HXFIILGF.js.map → chunk-4RASUAN3.js.map} +0 -0
  336. /package/dist/{chunk-EI6M5NVK.js.map → chunk-5JBVPJCT.js.map} +0 -0
  337. /package/dist/{chunk-4NV7O76Q.js.map → chunk-5ORJHH43.js.map} +0 -0
  338. /package/dist/{chunk-74AY3S7U.js.map → chunk-5RW247F5.js.map} +0 -0
  339. /package/dist/{chunk-4XINO77L.js.map → chunk-66JOLMWS.js.map} +0 -0
  340. /package/dist/{chunk-AYJQ644F.js.map → chunk-6KBCUIZP.js.map} +0 -0
  341. /package/dist/{chunk-GKL6QYZO.js.map → chunk-6L63IN3M.js.map} +0 -0
  342. /package/dist/{chunk-OPFVXM2D.js.map → chunk-7P5VBRBS.js.map} +0 -0
  343. /package/dist/{chunk-4SOWMA55.js.map → chunk-7WFY2L5J.js.map} +0 -0
  344. /package/dist/{chunk-GJ4HU6LK.js.map → chunk-AGRMXXVA.js.map} +0 -0
  345. /package/dist/{chunk-IVUVQPTQ.js.map → chunk-AZNE2H6D.js.map} +0 -0
  346. /package/dist/{chunk-HPB5TF55.js.map → chunk-BCBY5GYZ.js.map} +0 -0
  347. /package/dist/{chunk-M4IEZGCK.js.map → chunk-BFBS7OFQ.js.map} +0 -0
  348. /package/dist/{chunk-4PG5R6BJ.js.map → chunk-BNU5TQLR.js.map} +0 -0
  349. /package/dist/{chunk-TVE7W67N.js.map → chunk-BTU77RB5.js.map} +0 -0
  350. /package/dist/{chunk-H6VJSITJ.js.map → chunk-CIL4GHMD.js.map} +0 -0
  351. /package/dist/{chunk-KZZ5EZ7O.js.map → chunk-DC3DWF2M.js.map} +0 -0
  352. /package/dist/{chunk-AUFPPBC3.js.map → chunk-ENPJX3BB.js.map} +0 -0
  353. /package/dist/{chunk-CTH2CQCB.js.map → chunk-EQ6EMJRG.js.map} +0 -0
  354. /package/dist/{chunk-63A2BU2B.js.map → chunk-F4ZHKDBW.js.map} +0 -0
  355. /package/dist/{chunk-3LBDEGQA.js.map → chunk-FDUCIUQ5.js.map} +0 -0
  356. /package/dist/{chunk-OCK4D5PC.js.map → chunk-FGGLVCHI.js.map} +0 -0
  357. /package/dist/{chunk-XVZA6VXC.js.map → chunk-FZVROZKC.js.map} +0 -0
  358. /package/dist/{chunk-3RHU5MD7.js.map → chunk-GXAZ4Q2S.js.map} +0 -0
  359. /package/dist/{chunk-UVVFPCPX.js.map → chunk-H2X32F2K.js.map} +0 -0
  360. /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
  361. /package/dist/{chunk-W7K5BP3M.js.map → chunk-HQPFACXO.js.map} +0 -0
  362. /package/dist/{chunk-TECK5IVY.js.map → chunk-HZNURRDS.js.map} +0 -0
  363. /package/dist/{chunk-U2CAGMUD.js.map → chunk-ICBUTKLL.js.map} +0 -0
  364. /package/dist/{chunk-B4DXREWC.js.map → chunk-IQXQ3KWR.js.map} +0 -0
  365. /package/dist/{chunk-AL3TCNSJ.js.map → chunk-J6GZ2254.js.map} +0 -0
  366. /package/dist/{chunk-5MGKGEL7.js.map → chunk-J72ZY7NU.js.map} +0 -0
  367. /package/dist/{chunk-OH55F76C.js.map → chunk-JEX2JL3U.js.map} +0 -0
  368. /package/dist/{chunk-Y3XPSGNF.js.map → chunk-JGLZ46JS.js.map} +0 -0
  369. /package/dist/{chunk-EXQJMFRF.js.map → chunk-JY4LXMZR.js.map} +0 -0
  370. /package/dist/{chunk-6P7U3HWG.js.map → chunk-K36RMINQ.js.map} +0 -0
  371. /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
  372. /package/dist/{chunk-LN4X2NDA.js.map → chunk-KY3KJUNN.js.map} +0 -0
  373. /package/dist/{chunk-H5DIRCZP.js.map → chunk-LHMCZ6VW.js.map} +0 -0
  374. /package/dist/{chunk-GGD2YDS6.js.map → chunk-LN7CWLAU.js.map} +0 -0
  375. /package/dist/{chunk-GHHQLSSY.js.map → chunk-LYOCAUI6.js.map} +0 -0
  376. /package/dist/{chunk-W26D2USW.js.map → chunk-MUH7RR6T.js.map} +0 -0
  377. /package/dist/{chunk-IAGVM7EY.js.map → chunk-N2Y6COEM.js.map} +0 -0
  378. /package/dist/{chunk-64JLBFCQ.js.map → chunk-N7NAGECH.js.map} +0 -0
  379. /package/dist/{chunk-5Y6VA6X2.js.map → chunk-OGQGDOYK.js.map} +0 -0
  380. /package/dist/{chunk-JLMOH5JA.js.map → chunk-OWZXDBV4.js.map} +0 -0
  381. /package/dist/{chunk-ZORS5BG5.js.map → chunk-PJAMFIDE.js.map} +0 -0
  382. /package/dist/{chunk-SXTVZ6JL.js.map → chunk-QIOQI7QZ.js.map} +0 -0
  383. /package/dist/{chunk-75DCOS6I.js.map → chunk-QUPTDKSX.js.map} +0 -0
  384. /package/dist/{chunk-HH73M65H.js.map → chunk-QVZZE6VH.js.map} +0 -0
  385. /package/dist/{chunk-XWE3EOWX.js.map → chunk-RZED7FAU.js.map} +0 -0
  386. /package/dist/{chunk-IZBIDPMY.js.map → chunk-S37O4S6E.js.map} +0 -0
  387. /package/dist/{chunk-NHWDTOBN.js.map → chunk-S73ZVU37.js.map} +0 -0
  388. /package/dist/{chunk-YSATDEBY.js.map → chunk-SBRUTD5O.js.map} +0 -0
  389. /package/dist/{chunk-PRKNAIGZ.js.map → chunk-T6T2RMWW.js.map} +0 -0
  390. /package/dist/{chunk-CIEEYVUB.js.map → chunk-TPX7XXY4.js.map} +0 -0
  391. /package/dist/{chunk-L334PSLW.js.map → chunk-U2A56LLF.js.map} +0 -0
  392. /package/dist/{chunk-P4TV3TQ2.js.map → chunk-U6IL4724.js.map} +0 -0
  393. /package/dist/{chunk-5UM4H64E.js.map → chunk-UAOJVQXC.js.map} +0 -0
  394. /package/dist/{chunk-MOIJWWNA.js.map → chunk-UE5JYPU6.js.map} +0 -0
  395. /package/dist/{chunk-TGQW3AMK.js.map → chunk-UIFWZMO7.js.map} +0 -0
  396. /package/dist/{chunk-UW6LBFYN.js.map → chunk-USX5YIKB.js.map} +0 -0
  397. /package/dist/{chunk-QGFR73SN.js.map → chunk-VK5HKVQ7.js.map} +0 -0
  398. /package/dist/{chunk-IHCXMYSE.js.map → chunk-VPEIWMUT.js.map} +0 -0
  399. /package/dist/{chunk-O5ZINWW3.js.map → chunk-VSJ3ARFS.js.map} +0 -0
  400. /package/dist/{chunk-RLVI2M5Q.js.map → chunk-W4V7P5HP.js.map} +0 -0
  401. /package/dist/{chunk-ELHJK7LM.js.map → chunk-WBN66HPI.js.map} +0 -0
  402. /package/dist/{chunk-BJLCV2P3.js.map → chunk-WGRAB3KB.js.map} +0 -0
  403. /package/dist/{chunk-5KDDAQ6K.js.map → chunk-WN2DASTS.js.map} +0 -0
  404. /package/dist/{chunk-IG3EZ3TF.js.map → chunk-WNQ3B3Q5.js.map} +0 -0
  405. /package/dist/{chunk-V6SBA5O7.js.map → chunk-WYOHQJKC.js.map} +0 -0
  406. /package/dist/{chunk-WHROVQ5Q.js.map → chunk-X4DHAXFO.js.map} +0 -0
  407. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  408. /package/dist/{chunk-JLGBJ4AX.js.map → chunk-XPZM7GP6.js.map} +0 -0
  409. /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
  410. /package/dist/{chunk-UYUFDTIX.js.map → chunk-YIXWY4OZ.js.map} +0 -0
  411. /package/dist/{chunk-NCZGGGWI.js.map → chunk-YPYSC3TX.js.map} +0 -0
  412. /package/dist/{chunk-AVGDYYMA.js.map → chunk-ZBET3W26.js.map} +0 -0
@@ -1,11 +1,4 @@
1
- /**
2
- * Chunked trigger-drain for the extraction pipeline.
3
- *
4
- * Split out of `turn-ingestion.ts`: that file is at the structural ratchet's
5
- * line cap (issue #1995), and the drain is a self-contained unit — it only
6
- * needs the host's `runExtraction`, the buffer drop, the storage side-file
7
- * copy and the preemption hook.
8
- */
1
+ /** Chunked trigger-drain for the extraction pipeline. */
9
2
 
10
3
  import { createHash } from "node:crypto";
11
4
  import { isAbortError, preemptedExtractionError } from "../abort-error.js";
@@ -36,12 +29,18 @@ export interface ExtractionDrainDeps {
36
29
  extractionChunkDropAfterFailures: number;
37
30
  };
38
31
  extractionWindowBudgetChars?(): number;
32
+ /** Rendered-size oracle: the exact character count the extraction renderer */
33
+ measureRenderedTurns?(turns: readonly BufferTurn[]): number;
39
34
  runExtraction(turns: BufferTurn[], options: RunOptions): Promise<ExtractionRunResult>;
40
35
  getStorage(): Promise<{
41
36
  saveDroppedExtractionChunk(bufferKey: string, reason: string, turns: readonly BufferTurn[]): Promise<string>;
42
37
  }>;
43
38
  buffer: {
44
- dropExtractionChunk(bufferKey: string, turns: readonly BufferTurn[], options?: { allowNonPrefix?: boolean }): Promise<number>;
39
+ dropExtractionChunk(
40
+ bufferKey: string,
41
+ turns: readonly BufferTurn[],
42
+ options?: { allowNonPrefix?: boolean }
43
+ ): Promise<number>;
45
44
  };
46
45
  }
47
46
 
@@ -52,14 +51,28 @@ function chunkFingerprint(chunk: readonly BufferTurn[]): string {
52
51
  return hash.digest("hex");
53
52
  }
54
53
 
54
+ /** The chunking decision for one trigger-drain window. */
55
+ interface ChunkPlan {
56
+ singleRun: boolean;
57
+ chunks: BufferTurn[][];
58
+ resumed: boolean;
59
+ }
60
+
61
+ /** The drain-loop action for one chunk result. */
62
+ interface ChunkVerdict {
63
+ action: "continue" | "return" | "failed";
64
+ result: ExtractionRunResult;
65
+ aggregate: ExtractionRunResult | null;
66
+ }
67
+
68
+ /** Per-chunk run options plus the chunk-local preemption controller. */
69
+ interface ChunkExecutionOptions {
70
+ options: DrainRunOptions;
71
+ preempt?: AbortController;
72
+ }
73
+
55
74
  export class ExtractionDrain {
56
- /**
57
- * Consecutive failed attempts on the head chunk, per buffer key. In memory on
58
- * purpose (a restart re-counts from zero). Bound to the chunk's content: a
59
- * count recorded for one chunk never applies to a different one, so a force
60
- * flush that clears a failing chunk cannot make unrelated content inherit its
61
- * failures.
62
- */
75
+ /** Consecutive failed attempts on the head chunk, per buffer key. In memory on */
63
76
  private readonly failures = new Map<string, { fingerprint: string; count: number }>();
64
77
 
65
78
  /**
@@ -71,7 +84,15 @@ export class ExtractionDrain {
71
84
  */
72
85
  private readonly qualified = new Map<string, string>();
73
86
 
74
- constructor(private readonly deps: ExtractionDrainDeps) {}
87
+ /** The claim drain runs with `dropFailedChunks: false`: a chunk that keeps */
88
+ private readonly dropFailedChunks: boolean;
89
+
90
+ private readonly deps: ExtractionDrainDeps;
91
+
92
+ constructor(deps: ExtractionDrainDeps, options?: { dropFailedChunks?: boolean }) {
93
+ this.deps = deps;
94
+ this.dropFailedChunks = options?.dropFailedChunks !== false;
95
+ }
75
96
 
76
97
  /** Characters the coordinator would count for this window: user/assistant text, trimmed and capped per turn, context-only turns left out. */
77
98
  private windowChars(turns: readonly BufferTurn[]): number {
@@ -118,36 +139,77 @@ export class ExtractionDrain {
118
139
  turns: BufferTurn[],
119
140
  runOptions: DrainRunOptions,
120
141
  preemptable = false,
121
- preemptHandle?: ExtractionPreemptHandle,
142
+ preemptHandle?: ExtractionPreemptHandle
122
143
  ): Promise<ExtractionRunResult> {
123
144
  const budgetChars = this.deps.extractionWindowBudgetChars?.() ?? Number.POSITIVE_INFINITY;
124
- const chunkable =
125
- runOptions.clearBufferAfterExtraction &&
126
- !runOptions.forceExtractionAttempt &&
127
- !runOptions.failOnExtractionFailure &&
128
- !turns.some((turn) => turn.acceptedCapture === true) &&
129
- Number.isFinite(budgetChars) &&
130
- budgetChars > 0;
131
- const chunks = chunkable ? chunkExtractionWindow(turns, budgetChars) : [];
132
- const resumed =
133
- chunks.length > 0 && this.qualified.get(runOptions.bufferKey) === chunkFingerprint(chunks[0].slice(0, 1));
145
+ const plan = this.chunkPlan(turns, runOptions, budgetChars);
134
146
  // The tail of a window that already qualified goes through the chunk loop even when it now fits in one
135
147
  // chunk, so its failures still count toward the drop threshold.
136
- if (chunks.length <= 1 && !resumed) return await this.deps.runExtraction(turns, runOptions);
148
+ if (plan.singleRun) return await this.deps.runExtraction(turns, runOptions);
137
149
  // Eligibility (minimum user turns and characters) belongs to the whole
138
150
  // window: a window that never qualified keeps the single-run path, which
139
151
  // judges it exactly as before.
140
- if (!resumed) {
141
- const windowUserTurns = turns.filter((turn) => turn.role === "user").length;
142
- const belowChars = !runOptions.skipCharThreshold && this.windowChars(turns) < this.deps.config.extractionMinChars;
143
- if ((!runOptions.skipUserTurnThreshold && windowUserTurns < this.deps.config.extractionMinUserTurns) || belowChars) {
144
- return await this.deps.runExtraction(turns, runOptions);
145
- }
152
+ if (!plan.resumed && this.windowBelowThreshold(turns, runOptions)) {
153
+ return await this.deps.runExtraction(turns, runOptions);
146
154
  }
147
- // The window qualified as a whole; its chunks must not be re-judged one by
148
- // one (an assistant-only or short chunk would be cleared unextracted).
149
- const baseOptions = { ...runOptions, skipCharThreshold: true, skipUserTurnThreshold: true };
155
+ return await this.runChunked(plan.chunks, runOptions, preemptable, preemptHandle);
156
+ }
157
+
158
+ /** Chunking plan for one trigger-drain window. Accepted-capture groups */
159
+ private chunkPlan(
160
+ turns: readonly BufferTurn[],
161
+ runOptions: DrainRunOptions,
162
+ budgetChars: number
163
+ ): ChunkPlan {
164
+ const chunks = this.chunkable(runOptions, turns, budgetChars)
165
+ ? this.deps.measureRenderedTurns !== undefined
166
+ ? this.chunkByRenderedSize(turns, budgetChars)
167
+ : chunkExtractionWindow(turns, budgetChars)
168
+ : [];
169
+ const resumed =
170
+ chunks.length > 0 && this.qualified.get(runOptions.bufferKey) === chunkFingerprint(chunks[0].slice(0, 1));
171
+ return { singleRun: chunks.length <= 1 && !resumed, chunks, resumed };
172
+ }
173
+
174
+ /** Automatic chunking needs a clearable window, no fail-closed/forced
175
+ * caller, and a real budget to chunk against. */
176
+ private chunkable(runOptions: DrainRunOptions, turns: readonly BufferTurn[], budgetChars: number): boolean {
177
+ return (
178
+ runOptions.clearBufferAfterExtraction &&
179
+ !runOptions.forceExtractionAttempt &&
180
+ !runOptions.failOnExtractionFailure &&
181
+ this.identityCleared(runOptions, turns) &&
182
+ Number.isFinite(budgetChars) &&
183
+ budgetChars > 0
184
+ );
185
+ }
186
+
187
+ /** A native group clears its turns by identity (a pinned capture can sit
188
+ * in front); a drop must find them the same way. */
189
+ private identityCleared(runOptions: DrainRunOptions, turns: readonly BufferTurn[]): boolean {
190
+ return runOptions.clearMatchingTurns === true || !turns.some((turn) => turn.acceptedCapture === true);
191
+ }
150
192
 
193
+ /** Eligibility (minimum user turns and characters) belongs to the whole
194
+ * window: a window that never qualified keeps the single-run path, which
195
+ * judges it exactly as before. */
196
+ private windowBelowThreshold(turns: readonly BufferTurn[], runOptions: DrainRunOptions): boolean {
197
+ const windowUserTurns = turns.filter((turn) => turn.role === "user").length;
198
+ const belowChars = !runOptions.skipCharThreshold && this.windowChars(turns) < this.deps.config.extractionMinChars;
199
+ return (
200
+ (!runOptions.skipUserTurnThreshold && windowUserTurns < this.deps.config.extractionMinUserTurns) || belowChars
201
+ );
202
+ }
203
+
204
+ /** The window qualified as a whole; its chunks must not be re-judged one by
205
+ * one (an assistant-only or short chunk would be cleared unextracted). */
206
+ private async runChunked(
207
+ chunks: readonly BufferTurn[][],
208
+ runOptions: DrainRunOptions,
209
+ preemptable: boolean,
210
+ preemptHandle?: ExtractionPreemptHandle
211
+ ): Promise<ExtractionRunResult> {
212
+ const baseOptions = { ...runOptions, skipCharThreshold: true, skipUserTurnThreshold: true };
151
213
  const bufferKey = runOptions.bufferKey;
152
214
  // A native group clears its turns by identity (a pinned capture can sit in front); a drop must find them the same way.
153
215
  const byIdentity = runOptions.clearMatchingTurns === true;
@@ -155,78 +217,184 @@ export class ExtractionDrain {
155
217
  let aggregate: ExtractionRunResult | null = null;
156
218
  for (const [position, chunk] of chunks.entries()) {
157
219
  this.qualified.set(bufferKey, chunkFingerprint(chunk.slice(0, 1)));
158
- // retentionWindow: the chunks processed so far. A chunk's deferred retention replaces theirs (retained turns do not pile
159
- // up across chunks) and leaves the retained turns of chunks not yet processed in the buffer.
160
- let chunkOptions: DrainRunOptions = { ...baseOptions, retentionWindow: chunks.slice(0, position + 1).flat() };
161
- let preempt: AbortController | undefined;
162
- if (preemptable && position > 0) {
163
- const local = new AbortController();
164
- preempt = local;
165
- preemptHandle?.arm(() => local.abort(preemptedExtractionError()));
166
- chunkOptions = {
167
- ...chunkOptions,
168
- abortSignal: runOptions.abortSignal ? AbortSignal.any([runOptions.abortSignal, local.signal]) : local.signal,
169
- };
170
- }
171
- let failures = this.failureCount(bufferKey, chunk);
220
+ const { options: chunkOptions, preempt } = this.chunkOptionsFor(
221
+ chunks,
222
+ position,
223
+ baseOptions,
224
+ runOptions,
225
+ preemptable,
226
+ preemptHandle
227
+ );
228
+ const failures = this.failureCount(bufferKey, chunk);
172
229
  let result: ExtractionRunResult;
173
230
  try {
174
231
  result = await this.deps.runExtraction(chunk, chunkOptions);
175
232
  } catch (err) {
176
- // The chunk-local abort leaves the outer signal untouched, so hand back the
177
- // preemption reason the host looks for (as it does for the first chunk).
178
- if (isAbortError(err)) throw preempt && preempt.signal.aborted && !runOptions.abortSignal?.aborted ? preempt.signal.reason : err;
179
- failures += 1;
180
- if (failures < dropAfter) {
181
- this.setFailureCount(bufferKey, chunk, failures);
182
- throw err;
183
- }
184
- // A chunk that keeps throwing is dropped like one that keeps failing,
185
- // so later chunks are not stuck behind it.
186
- const thrown: ExtractionRunResult = {
187
- status: "skipped",
188
- reason: `thrown: ${describeError(err)}`,
189
- persistedCount: 0,
190
- durableOutputCount: 0,
191
- };
192
- if (!(await this.dropFailedChunk(bufferKey, chunk, dropAfter, thrown, byIdentity))) {
193
- this.setFailureCount(bufferKey, chunk, failures);
194
- throw err;
195
- }
196
- this.setFailureCount(bufferKey, chunk, 0);
233
+ this.rethrowChunkAbort(err, preempt, runOptions.abortSignal?.aborted === true);
234
+ // A successfully dropped thrown chunk does not end the drain: later
235
+ // chunks still run this pass (live drop policy).
236
+ aggregate = await this.settleThrownChunk(bufferKey, chunk, failures, dropAfter, err, byIdentity, aggregate);
197
237
  continue;
198
238
  }
199
- const verdict = classifyDrainResult(result);
200
- if (verdict === "drained") {
201
- this.setFailureCount(bufferKey, chunk, 0);
202
- aggregate = mergeDrainResults(aggregate, result);
239
+ const settled = this.settleVerdict(bufferKey, chunk, result, aggregate);
240
+ if (settled.action === "continue") {
241
+ aggregate = settled.result;
203
242
  continue;
204
243
  }
205
- // Provider breaker/backoff retention: keep everything for retry.
206
- if (verdict === "deferred") return mergeDrainResults(aggregate, result);
207
- // An outage or a bad key is not the chunk's fault: keep the chunk and do not count toward dropping it.
208
- if (result.failureClass === "provider_retryable" || result.failureClass === "auth_config") {
209
- return mergeDrainResults(aggregate, result);
210
- }
211
- failures += 1;
212
- if (failures >= dropAfter) {
213
- if (!(await this.dropFailedChunk(bufferKey, chunk, dropAfter, result, byIdentity))) {
214
- // No recovery copy: keep the only buffered copy and retry later.
215
- this.setFailureCount(bufferKey, chunk, failures);
216
- return mergeDrainResults(aggregate, result);
217
- }
218
- this.setFailureCount(bufferKey, chunk, 0);
219
- aggregate = mergeDrainResults(aggregate, result);
244
+ if (settled.action === "return") return settled.result;
245
+ const failed = await this.settleFailedChunk({
246
+ bufferKey,
247
+ chunk,
248
+ failures,
249
+ dropAfter,
250
+ result: settled.result,
251
+ byIdentity,
252
+ aggregate: settled.aggregate,
253
+ });
254
+ if (failed.action === "continue") {
255
+ aggregate = failed.result;
220
256
  continue;
221
257
  }
222
- this.setFailureCount(bufferKey, chunk, failures);
223
- // Retain the remaining window for the next trigger (existing behavior).
224
- return mergeDrainResults(aggregate, result);
258
+ return failed.result;
225
259
  }
226
260
  this.qualified.delete(bufferKey);
227
261
  return aggregate ?? { status: "skipped", persistedCount: 0, durableOutputCount: 0 };
228
262
  }
229
263
 
264
+ /** retention replaces theirs (retained turns do not pile up across chunks) */
265
+ private chunkOptionsFor(
266
+ chunks: readonly BufferTurn[][],
267
+ position: number,
268
+ baseOptions: DrainRunOptions,
269
+ runOptions: DrainRunOptions,
270
+ preemptable: boolean,
271
+ preemptHandle?: ExtractionPreemptHandle
272
+ ): ChunkExecutionOptions {
273
+ let options: DrainRunOptions = { ...baseOptions, retentionWindow: chunks.slice(0, position + 1).flat() };
274
+ let preempt: AbortController | undefined;
275
+ if (preemptable && position > 0) {
276
+ preempt = this.armChunkPreempt(preemptHandle);
277
+ options = {
278
+ ...options,
279
+ abortSignal: runOptions.abortSignal
280
+ ? AbortSignal.any([runOptions.abortSignal, preempt.signal])
281
+ : preempt.signal,
282
+ };
283
+ }
284
+ return { options, preempt };
285
+ }
286
+
287
+ /** A chunk-local abort controller so a fail-closed flush can abort the
288
+ * next chunk instead of waiting behind the whole window. */
289
+ private armChunkPreempt(preemptHandle?: ExtractionPreemptHandle): AbortController {
290
+ const local = new AbortController();
291
+ preemptHandle?.arm(() => local.abort(preemptedExtractionError()));
292
+ return local;
293
+ }
294
+
295
+ /** An abort error is rethrown (the chunk-local abort leaves the outer
296
+ * signal untouched, so the host gets the preemption reason it looks for,
297
+ * as it does for the first chunk). */
298
+ private rethrowChunkAbort(cause: unknown, preempt: AbortController | undefined, outerAborted: boolean): void {
299
+ if (!isAbortError(cause)) return;
300
+ throw preempt && preempt.signal.aborted && !outerAborted ? preempt.signal.reason : cause;
301
+ }
302
+
303
+ /** A chunk whose extraction THREW: count the failure; below the drop
304
+ * threshold (or on a no-drop drain) rethrow so the window is retained;
305
+ * at the threshold a chunk that keeps throwing is dropped like one that
306
+ * keeps failing, so later chunks are not stuck behind it. */
307
+ private async settleThrownChunk(
308
+ bufferKey: string,
309
+ chunk: readonly BufferTurn[],
310
+ failures: number,
311
+ dropAfter: number,
312
+ cause: unknown,
313
+ byIdentity: boolean,
314
+ aggregate: ExtractionRunResult | null
315
+ ): Promise<ExtractionRunResult> {
316
+ failures += 1;
317
+ if (failures < dropAfter || !this.dropFailedChunks) {
318
+ this.setFailureCount(bufferKey, chunk, failures);
319
+ throw cause;
320
+ }
321
+ const thrown: ExtractionRunResult = {
322
+ status: "skipped",
323
+ reason: `thrown: ${describeError(cause)}`,
324
+ persistedCount: 0,
325
+ durableOutputCount: 0,
326
+ };
327
+ if (!(await this.dropFailedChunk(bufferKey, chunk, dropAfter, thrown, byIdentity))) {
328
+ this.setFailureCount(bufferKey, chunk, failures);
329
+ throw cause;
330
+ }
331
+ this.setFailureCount(bufferKey, chunk, 0);
332
+ return mergeDrainResults(aggregate, thrown);
333
+ }
334
+
335
+ /** Map one chunk result to the drain-loop action: drained chunks continue
336
+ * to the next chunk; breaker/backoff retention and provider faults keep
337
+ * everything for retry without counting a chunk failure. */
338
+ private settleVerdict(
339
+ bufferKey: string,
340
+ chunk: readonly BufferTurn[],
341
+ result: ExtractionRunResult,
342
+ aggregate: ExtractionRunResult | null
343
+ ): ChunkVerdict {
344
+ const verdict = classifyDrainResult(result);
345
+ if (verdict === "drained") {
346
+ this.setFailureCount(bufferKey, chunk, 0);
347
+ return { action: "continue", result: mergeDrainResults(aggregate, result), aggregate };
348
+ }
349
+ // Provider breaker/backoff retention: keep everything for retry.
350
+ if (verdict === "deferred") return { action: "return", result: mergeDrainResults(aggregate, result), aggregate };
351
+ // An outage or a bad key is not the chunk's fault: keep the chunk and do not count toward dropping it.
352
+ if (result.failureClass === "provider_retryable" || result.failureClass === "auth_config") {
353
+ return { action: "return", result: mergeDrainResults(aggregate, result), aggregate };
354
+ }
355
+ return { action: "failed", result, aggregate };
356
+ }
357
+
358
+ /** (live drains, recovery copy first) or keep it as durable retryable */
359
+ private async settleFailedChunk(args: {
360
+ bufferKey: string;
361
+ chunk: readonly BufferTurn[];
362
+ failures: number;
363
+ dropAfter: number;
364
+ result: ExtractionRunResult;
365
+ byIdentity: boolean;
366
+ aggregate: ExtractionRunResult | null;
367
+ }): Promise<{ action: "continue" | "return"; result: ExtractionRunResult }> {
368
+ const failures = args.failures + 1;
369
+ if (failures >= args.dropAfter && this.dropFailedChunks) {
370
+ if (!(await this.dropFailedChunk(args.bufferKey, args.chunk, args.dropAfter, args.result, args.byIdentity))) {
371
+ // No recovery copy: keep the only buffered copy and retry later.
372
+ this.setFailureCount(args.bufferKey, args.chunk, failures);
373
+ return { action: "return", result: mergeDrainResults(args.aggregate, args.result) };
374
+ }
375
+ this.setFailureCount(args.bufferKey, args.chunk, 0);
376
+ return { action: "continue", result: mergeDrainResults(args.aggregate, args.result) };
377
+ }
378
+ this.setFailureCount(args.bufferKey, args.chunk, failures);
379
+ return { action: "return", result: mergeDrainResults(args.aggregate, args.result) };
380
+ }
381
+
382
+ /** Greedy chunking sized by the RENDERED conversation (the oracle the model */
383
+ private chunkByRenderedSize(turns: readonly BufferTurn[], budgetChars: number): BufferTurn[][] {
384
+ const measure = this.deps.measureRenderedTurns!;
385
+ const chunks: BufferTurn[][] = [];
386
+ let current: BufferTurn[] = [];
387
+ for (const turn of turns) {
388
+ if (current.length > 0 && measure([...current, turn]) > budgetChars) {
389
+ chunks.push(current);
390
+ current = [];
391
+ }
392
+ current.push(turn);
393
+ }
394
+ if (current.length > 0) chunks.push(current);
395
+ return chunks;
396
+ }
397
+
230
398
  /** Side-file copy first (content survives a failed drop), then remove the failing chunk from the buffer. Never throws:
231
399
  * the drop must not fail the extraction task that already settled its result. */
232
400
  private async dropFailedChunk(
@@ -234,22 +402,14 @@ export class ExtractionDrain {
234
402
  chunk: readonly BufferTurn[],
235
403
  dropAfter: number,
236
404
  result: ExtractionRunResult,
237
- byIdentity: boolean,
405
+ byIdentity: boolean
238
406
  ): Promise<boolean> {
239
407
  const reason =
240
408
  `dropped after ${dropAfter} repeated extraction failures ` +
241
409
  `(last result: status=${result.status} reason=${result.reason ?? "n/a"})`;
242
- try {
243
- const storage = await this.deps.getStorage();
244
- const path = await storage.saveDroppedExtractionChunk(bufferKey, reason, chunk);
245
- log.warn(`buffer[${bufferKey}]: dropping ${chunk.length} turns — ${reason}; copy saved to ${path}`);
246
- } catch (err) {
247
- // Never destroy the only buffered copy without a recovery file.
248
- log.warn(
249
- `buffer[${bufferKey}]: could not save the dropped-chunk recovery copy; keeping the chunk in the buffer: ${describeError(err)}`,
250
- );
251
- return false;
252
- }
410
+ const copyPath = await this.saveRecoveryCopy(bufferKey, reason, chunk);
411
+ if (copyPath === null) return false;
412
+ log.warn(`buffer[${bufferKey}]: dropping ${chunk.length} turns — ${reason}; copy saved to ${copyPath}`);
253
413
  try {
254
414
  const dropped = await this.deps.buffer.dropExtractionChunk(bufferKey, chunk, { allowNonPrefix: byIdentity });
255
415
  if (dropped > 0) {
@@ -260,9 +420,28 @@ export class ExtractionDrain {
260
420
  log.warn(`buffer[${bufferKey}]: dropped chunk no longer matches the buffer; keeping it and its failure count`);
261
421
  } catch (err) {
262
422
  log.warn(
263
- `buffer[${bufferKey}]: failed to remove dropped chunk from buffer (will retry next trigger): ${describeError(err)}`,
423
+ `buffer[${bufferKey}]: failed to remove dropped chunk from buffer (will retry next trigger): ${describeError(err)}`
264
424
  );
265
425
  }
266
426
  return false;
267
427
  }
428
+
429
+ /** The recovery side-file copy for a dropped chunk. null = the copy could
430
+ * not be written — the caller must keep the only buffered copy. */
431
+ private async saveRecoveryCopy(
432
+ bufferKey: string,
433
+ reason: string,
434
+ chunk: readonly BufferTurn[]
435
+ ): Promise<string | null> {
436
+ try {
437
+ const storage = await this.deps.getStorage();
438
+ return await storage.saveDroppedExtractionChunk(bufferKey, reason, chunk);
439
+ } catch (err) {
440
+ // Never destroy the only buffered copy without a recovery file.
441
+ log.warn(
442
+ `buffer[${bufferKey}]: could not save the dropped-chunk recovery copy; keeping the chunk in the buffer: ${describeError(err)}`
443
+ );
444
+ return null;
445
+ }
446
+ }
268
447
  }
@@ -79,6 +79,13 @@ export class ExtractionQueueCoordinator {
79
79
  private accepting = true;
80
80
  /** Buffer keys with a task currently running (per-key exclusion). */
81
81
  private readonly activeKeys = new Set<string>();
82
+ /**
83
+ * Buffer keys with a claim DECISION in flight (daemon backlog scheduler).
84
+ * A reservation is taken synchronously BEFORE the async claim/decision so
85
+ * no queued task for the key can start past the gate while the decision is
86
+ * unsettled; selection skips reserved keys until release.
87
+ */
88
+ private readonly reservedKeys = new Set<string>();
82
89
  /** Preempt slots of the currently running tasks, keyed by slot identity. */
83
90
  private readonly preemptSlots = new Map<object, PreemptSlot>();
84
91
  private preemptSeq = 0;
@@ -133,6 +140,44 @@ export class ExtractionQueueCoordinator {
133
140
  return true;
134
141
  }
135
142
 
143
+ /**
144
+ * Synchronously reserve `key` so no task for it starts until the key's
145
+ * owner (the backlog scheduler's claim decision) settles and releases.
146
+ * Returns false when the key is active, queued, or already reserved —
147
+ * single-threaded JS makes this check+add atomic.
148
+ */
149
+ tryReserveKey(key: string): boolean {
150
+ if (this.activeKeys.has(key) || this.reservedKeys.has(key)) return false;
151
+ if (this.queue.some((entry) => entry.key === key)) return false;
152
+ this.reservedKeys.add(key);
153
+ // The processor may be parked with ONLY reserved entries left: restart
154
+ // it so selection is re-evaluated (it will skip the reserved key).
155
+ if (!this.processing && this.queue.length > 0) {
156
+ this.processing = true;
157
+ this.processQueue().catch((err) => {
158
+ this.logExtractionQueueFailure(err, "processor");
159
+ this.processing = false;
160
+ });
161
+ }
162
+ return true;
163
+ }
164
+
165
+ /** Settle a reservation: the key's decision is made and enqueued work (if
166
+ * any) now carries the key authority itself. Idempotent; wakes a parked
167
+ * processor waiting only on this release. */
168
+ releaseKeyReservation(key: string): void {
169
+ if (!this.reservedKeys.delete(key)) return;
170
+ if (this.queue.length > 0 && !this.processing) {
171
+ this.processing = true;
172
+ this.processQueue().catch((err) => {
173
+ this.logExtractionQueueFailure(err, "processor");
174
+ this.processing = false;
175
+ });
176
+ } else {
177
+ this.wake?.();
178
+ }
179
+ }
180
+
136
181
  preemptRunning(preferredKey?: string): void {
137
182
  const armed = Array.from(this.preemptSlots.values()).filter((slot) => slot.armed !== undefined);
138
183
  if (armed.length === 0) return;
@@ -200,7 +245,10 @@ export class ExtractionQueueCoordinator {
200
245
  }
201
246
  // Park until a task settles OR a new task is enqueued. A queued task
202
247
  // whose key is still held always implies an in-flight task holding
203
- // it, so inFlight is non-empty here whenever the loop must wait.
248
+ // it, so inFlight is non-empty here whenever the loop must wait. A
249
+ // queued task behind a RESERVATION (no in-flight holder) leaves the
250
+ // loop instead; releaseKeyReservation and enqueue restart the
251
+ // processor when it is not running.
204
252
  if (inFlight.size === 0) break;
205
253
  const { promise: wakePromise, resolve: wake } = Promise.withResolvers<void>();
206
254
  this.wake = wake;
@@ -215,11 +263,11 @@ export class ExtractionQueueCoordinator {
215
263
  }
216
264
  }
217
265
 
218
- /** Take the next queue entry whose key is not already running (FIFO within a key). */
266
+ /** Take the next queue entry whose key is not already running or reserved (FIFO within a key). */
219
267
  private takeRunnable(): QueueEntry | undefined {
220
268
  for (let index = 0; index < this.queue.length; index += 1) {
221
269
  const entry = this.queue[index];
222
- if (entry.key !== undefined && this.activeKeys.has(entry.key)) continue;
270
+ if (entry.key !== undefined && (this.activeKeys.has(entry.key) || this.reservedKeys.has(entry.key))) continue;
223
271
  this.queue.splice(index, 1);
224
272
  if (entry.key !== undefined) this.activeKeys.add(entry.key);
225
273
  return entry;
@@ -24,6 +24,7 @@ import {
24
24
  } from "./extraction-run.js";
25
25
  import { StorageManager } from "../storage.js";
26
26
  import { parseConfig } from "../config.js";
27
+ import { ProviderCircuitBreaker } from "./provider-breaker.js";
27
28
  import type { ExtractionEngine } from "../extraction.js";
28
29
  import type { ThreadingManager } from "../threading.js";
29
30
  import type { BufferTurn, ExtractionResult, ExtractionFailureClass, PluginConfig } from "../types.js";
@@ -102,6 +103,7 @@ interface Harness {
102
103
  fn: ExtractionRunCoordinatorDeps["recordProcessedExtractionFingerprint"],
103
104
  ) => void;
104
105
  recordedProcessedCount: () => number;
106
+ makeDeps: () => ExtractionRunCoordinatorDeps;
105
107
  run: (
106
108
  coord: ExtractionRunCoordinator,
107
109
  content: string,
@@ -233,6 +235,7 @@ async function makeHarness(overrides: Record<string, unknown> = {}): Promise<Har
233
235
  persistHandler = fn;
234
236
  },
235
237
  recordedProcessedCount: () => recordedProcessedCount,
238
+ makeDeps,
236
239
  passiveCapture: () => passiveCapture,
237
240
  run: async (coord, content, opts = {}) => {
238
241
  const result = await coord.runExtraction(makeTurns(content), {
@@ -1416,3 +1419,41 @@ test("the retention window a chunk run is given reaches the buffer as the proces
1416
1419
  await h.cleanup();
1417
1420
  }
1418
1421
  });
1422
+
1423
+ test("isolated claim drains share the orchestrator's provider breaker (review round)", async () => {
1424
+ const harness = await makeHarness({
1425
+ extractionBreakerFailureThreshold: 1,
1426
+ extractionBreakerCooldownMs: 60_000,
1427
+ });
1428
+ try {
1429
+ // extractionRunCoordinatorForBuffer builds a fresh coordinator per claim
1430
+ // drain; the ORCHESTRATOR-wide breaker must be the one they all consult.
1431
+ const shared = new ProviderCircuitBreaker();
1432
+ const makeIsolated = () =>
1433
+ new ExtractionRunCoordinator({ ...harness.makeDeps(), providerBreaker: shared });
1434
+ harness.setRespond(() => ({
1435
+ facts: [],
1436
+ profileUpdates: [],
1437
+ entities: [],
1438
+ questions: [],
1439
+ extractionFailure: "provider down",
1440
+ extractionFailureClass: "provider_retryable",
1441
+ }));
1442
+
1443
+ // The first failing attempt opens the SHARED breaker.
1444
+ const first = await harness.run(makeIsolated(), "breaker-share");
1445
+ assert.equal(first.reason, "empty_extraction_result");
1446
+
1447
+ // A DIFFERENT isolated coordinator (a fresh claim drain) must observe the
1448
+ // opened breaker instead of re-calling the failing provider. Distinct
1449
+ // fingerprint, so only the breaker can suppress this attempt.
1450
+ const second = await harness.run(makeIsolated(), "breaker-share-2");
1451
+ assert.equal(
1452
+ second.reason,
1453
+ "provider_circuit_open",
1454
+ "a fresh isolated drain must honor the orchestrator-wide breaker instead of hammering the failing provider",
1455
+ );
1456
+ } finally {
1457
+ await harness.cleanup();
1458
+ }
1459
+ });