@remnic/core 10.0.65 → 10.0.67

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 (262) hide show
  1. package/dist/access-admin-ops-surface.js +16 -16
  2. package/dist/access-authorization-probe.js +17 -17
  3. package/dist/access-boundary.js +16 -16
  4. package/dist/access-cli.js +61 -61
  5. package/dist/access-extraction-force-flush.js +16 -16
  6. package/dist/access-http-briefing.js +17 -17
  7. package/dist/access-http-query.js +18 -18
  8. package/dist/access-http.js +33 -33
  9. package/dist/access-identity-continuity-surface.js +16 -16
  10. package/dist/access-lcm-surface.js +16 -16
  11. package/dist/access-mcp.js +26 -26
  12. package/dist/access-namespace-preflight.js +16 -16
  13. package/dist/access-observe-write-surface.js +16 -16
  14. package/dist/access-operations-batch.js +17 -17
  15. package/dist/access-operations.js +24 -24
  16. package/dist/access-recall-concurrency.js +16 -16
  17. package/dist/access-recall-diagnostics.js +2 -2
  18. package/dist/access-recall-response.js +16 -16
  19. package/dist/access-recall-surface.js +16 -16
  20. package/dist/access-service.js +16 -16
  21. package/dist/access-surface-catalog.js +18 -18
  22. package/dist/active-recall.js +3 -3
  23. package/dist/briefing.js +3 -3
  24. package/dist/buffer.js +2 -2
  25. package/dist/causal-consolidation.js +4 -4
  26. package/dist/{chunk-KISOMGY2.js → chunk-3RHU5MD7.js} +2 -2
  27. package/dist/{chunk-3LSTL6EY.js → chunk-4MPAKYZU.js} +2 -2
  28. package/dist/{chunk-OVL5AD3N.js → chunk-4NV7O76Q.js} +2 -2
  29. package/dist/{chunk-BH7NVSEC.js → chunk-4SOWMA55.js} +3 -3
  30. package/dist/{chunk-GROHOWR5.js → chunk-5MGKGEL7.js} +2 -2
  31. package/dist/{chunk-ZPLQ27MY.js → chunk-5UM4H64E.js} +2 -2
  32. package/dist/{chunk-2DNFRLLL.js → chunk-5Y6VA6X2.js} +2 -2
  33. package/dist/{chunk-GRAUQXQB.js → chunk-6WKWL53V.js} +13 -13
  34. package/dist/{chunk-HOR4RMUM.js → chunk-75DCOS6I.js} +24 -24
  35. package/dist/{chunk-25QK3LR2.js → chunk-7O2MC42R.js} +2 -2
  36. package/dist/{chunk-7ETWIBTO.js → chunk-ADYP7HXE.js} +3 -3
  37. package/dist/{chunk-FUDKGJDN.js → chunk-AVGDYYMA.js} +2 -2
  38. package/dist/{chunk-ECF627KC.js → chunk-AYJQ644F.js} +2 -2
  39. package/dist/{chunk-T22FBDG3.js → chunk-B3MWELZ3.js} +2 -2
  40. package/dist/{chunk-DRTSPRWL.js → chunk-B4DXREWC.js} +2 -2
  41. package/dist/{chunk-5UEGL5BF.js → chunk-CI3MAAIY.js} +2 -2
  42. package/dist/{chunk-7JYK7NNY.js → chunk-CTH2CQCB.js} +2 -2
  43. package/dist/{chunk-UVHXLVFJ.js → chunk-EURPSF4Y.js} +2 -2
  44. package/dist/{chunk-VPEA63FC.js → chunk-EWJZZDRG.js} +2 -2
  45. package/dist/{chunk-BOZQAUAJ.js → chunk-EXQJMFRF.js} +2 -2
  46. package/dist/{chunk-UH6ROXFJ.js → chunk-GGD2YDS6.js} +2 -2
  47. package/dist/{chunk-5JABRT5G.js → chunk-GHHQLSSY.js} +3 -3
  48. package/dist/{chunk-N4T5FW6U.js → chunk-GJ4HU6LK.js} +2 -2
  49. package/dist/{chunk-HRRAOTOQ.js → chunk-H5DIRCZP.js} +12 -12
  50. package/dist/{chunk-434E4BTO.js → chunk-HH73M65H.js} +3 -3
  51. package/dist/{chunk-LIGZO7XN.js → chunk-HPB5TF55.js} +2 -2
  52. package/dist/{chunk-E4QIX2OB.js → chunk-IAGVM7EY.js} +2 -2
  53. package/dist/{chunk-YJQJRXUI.js → chunk-IG3EZ3TF.js} +2 -2
  54. package/dist/{chunk-KNMSD2MQ.js → chunk-IVUVQPTQ.js} +2 -2
  55. package/dist/{chunk-XOFIJGB2.js → chunk-IZBIDPMY.js} +2 -2
  56. package/dist/{chunk-HFEGRMFG.js → chunk-JLGBJ4AX.js} +7 -7
  57. package/dist/{chunk-BBHKN2CN.js → chunk-JLMOH5JA.js} +2 -2
  58. package/dist/{chunk-TPCRDPUY.js → chunk-KZSGBWOJ.js} +2 -2
  59. package/dist/{chunk-SHHZWSSE.js → chunk-KZZ5EZ7O.js} +2 -2
  60. package/dist/{chunk-2ZSRACNK.js → chunk-LFYEQK45.js} +682 -549
  61. package/dist/chunk-LFYEQK45.js.map +1 -0
  62. package/dist/{chunk-FKGLJJVS.js → chunk-LN4X2NDA.js} +2 -2
  63. package/dist/{chunk-5ZHFPUBC.js → chunk-MOIJWWNA.js} +8 -8
  64. package/dist/{chunk-KO224R36.js → chunk-NCZGGGWI.js} +2 -2
  65. package/dist/{chunk-XVQVF2TK.js → chunk-NHWDTOBN.js} +269 -230
  66. package/dist/chunk-NHWDTOBN.js.map +1 -0
  67. package/dist/{chunk-7ZTGMEXF.js → chunk-O5ZINWW3.js} +2 -2
  68. package/dist/{chunk-WWH7GJ42.js → chunk-OCK4D5PC.js} +2 -2
  69. package/dist/{chunk-G6L3REL3.js → chunk-OPFVXM2D.js} +2 -2
  70. package/dist/{chunk-FR7PK7ED.js → chunk-P4TV3TQ2.js} +2 -2
  71. package/dist/{chunk-P2EM5LKH.js → chunk-PMDOGXA2.js} +2 -2
  72. package/dist/{chunk-NM2II57L.js → chunk-PRKNAIGZ.js} +2 -1
  73. package/dist/chunk-PRKNAIGZ.js.map +1 -0
  74. package/dist/{chunk-MRF5V5CD.js → chunk-QGFR73SN.js} +2 -2
  75. package/dist/{chunk-6D3IWYNY.js → chunk-R2BIVU2Z.js} +354 -292
  76. package/dist/chunk-R2BIVU2Z.js.map +1 -0
  77. package/dist/{chunk-HG3CG557.js → chunk-RLVI2M5Q.js} +2 -2
  78. package/dist/{chunk-HHL4MX3W.js → chunk-SXTVZ6JL.js} +2 -2
  79. package/dist/{chunk-CLSKGTAT.js → chunk-TECK5IVY.js} +2 -2
  80. package/dist/{chunk-SNS3DWG6.js → chunk-TVE7W67N.js} +2 -2
  81. package/dist/{chunk-KYSSUY66.js → chunk-U2CAGMUD.js} +2 -2
  82. package/dist/{chunk-XLCEFUAU.js → chunk-UVVFPCPX.js} +2 -2
  83. package/dist/{chunk-KQJUEQ2E.js → chunk-UW6LBFYN.js} +5 -5
  84. package/dist/{chunk-SEA56G6S.js → chunk-V4XE263I.js} +1 -1
  85. package/dist/chunk-V4XE263I.js.map +1 -0
  86. package/dist/{chunk-JL5HXL25.js → chunk-V6SBA5O7.js} +3 -3
  87. package/dist/{chunk-R7WYAXPO.js → chunk-VHSQU3RO.js} +81 -42
  88. package/dist/chunk-VHSQU3RO.js.map +1 -0
  89. package/dist/{chunk-B7VD36TI.js → chunk-WHROVQ5Q.js} +3 -3
  90. package/dist/{chunk-LDHR465K.js → chunk-Y3XPSGNF.js} +2 -2
  91. package/dist/{chunk-JPY3RMG2.js → chunk-ZORS5BG5.js} +4 -4
  92. package/dist/{chunk-KXY4HW2Y.js → chunk-ZXF3O7P4.js} +2 -2
  93. package/dist/cli.js +47 -47
  94. package/dist/coding/export-okf-codegraph.js +2 -2
  95. package/dist/compounding/engine.js +4 -4
  96. package/dist/config.d.ts.map +1 -1
  97. package/dist/config.js +3 -3
  98. package/dist/connectors/codex-materialize-runner.js +3 -3
  99. package/dist/connectors/index.js +3 -3
  100. package/dist/convergence-refresh.js +5 -5
  101. package/dist/corpus-watermark.js +5 -5
  102. package/dist/entity-retrieval.js +3 -3
  103. package/dist/external-wiki-access.js +17 -17
  104. package/dist/extraction.js +6 -6
  105. package/dist/harmonic-retrieval.js +3 -3
  106. package/dist/index.js +61 -61
  107. package/dist/location/index.js +5 -5
  108. package/dist/maintenance/memory-governance.js +3 -3
  109. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  110. package/dist/maintenance/rebuild-memory-projection-runner.js +5 -5
  111. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  112. package/dist/memory-browse-operations.js +17 -17
  113. package/dist/namespaces/migrate.js +4 -4
  114. package/dist/namespaces/storage.js +3 -3
  115. package/dist/operator-doctor-corpus.js +6 -6
  116. package/dist/operator-toolkit.js +12 -12
  117. package/dist/orchestration/buffer-drain-runner.js +61 -61
  118. package/dist/orchestration/consolidation-run.d.ts.map +1 -1
  119. package/dist/orchestration/extraction-drain.d.ts +4 -4
  120. package/dist/orchestration/extraction-drain.d.ts.map +1 -1
  121. package/dist/orchestration/extraction-persist-deps.d.ts +0 -2
  122. package/dist/orchestration/extraction-persist-deps.d.ts.map +1 -1
  123. package/dist/orchestration/extraction-persist.d.ts +10 -4
  124. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  125. package/dist/orchestration/extraction-queue-coordinator.d.ts +67 -33
  126. package/dist/orchestration/extraction-queue-coordinator.d.ts.map +1 -1
  127. package/dist/orchestration/extraction-run-helpers.d.ts +3 -2
  128. package/dist/orchestration/extraction-run-helpers.d.ts.map +1 -1
  129. package/dist/orchestration/extraction-run.d.ts +4 -14
  130. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  131. package/dist/orchestration/fact-dedup-window.d.ts +49 -0
  132. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -0
  133. package/dist/orchestration/maintenance.js +13 -13
  134. package/dist/orchestration/provider-breaker.d.ts +58 -0
  135. package/dist/orchestration/provider-breaker.d.ts.map +1 -0
  136. package/dist/orchestrator.d.ts +2 -17
  137. package/dist/orchestrator.d.ts.map +1 -1
  138. package/dist/orchestrator.js +61 -61
  139. package/dist/recall-explain-renderer.js +3 -3
  140. package/dist/recall-navigate-access.js +2 -2
  141. package/dist/recall-navigate-disclosure.js +2 -2
  142. package/dist/recall-navigate.js +2 -2
  143. package/dist/recall-navigation-operations.js +17 -17
  144. package/dist/recall-navigation.js +16 -16
  145. package/dist/recall-xray-cli.js +4 -4
  146. package/dist/recall-xray-renderer.js +3 -3
  147. package/dist/recall-xray.js +2 -2
  148. package/dist/resume-bundles.js +4 -4
  149. package/dist/semantic-consolidation.js +4 -4
  150. package/dist/semantic-rule-promotion.js +3 -3
  151. package/dist/semantic-rule-verifier.js +3 -3
  152. package/dist/source-agent-qualifier.js +5 -5
  153. package/dist/standup/build.js +3 -3
  154. package/dist/storage/profile-mutation.d.ts +25 -0
  155. package/dist/storage/profile-mutation.d.ts.map +1 -0
  156. package/dist/storage.d.ts +4 -6
  157. package/dist/storage.d.ts.map +1 -1
  158. package/dist/storage.js +2 -2
  159. package/dist/support-passport/index.js +19 -19
  160. package/dist/threading.d.ts +21 -4
  161. package/dist/threading.d.ts.map +1 -1
  162. package/dist/threading.js +1 -1
  163. package/dist/transfer/export-okf.js +3 -3
  164. package/dist/types.d.ts +2 -0
  165. package/dist/types.d.ts.map +1 -1
  166. package/dist/types.js +1 -1
  167. package/dist/verified-recall.js +3 -3
  168. package/dist/who-knows-cli.js +4 -4
  169. package/dist/who-knows.js +3 -3
  170. package/package.json +2 -2
  171. package/src/access-observe-replay-durability.test.ts +1 -1
  172. package/src/buffer-drain-handover.test.ts +3 -3
  173. package/src/buffer-drop-extraction-chunk.test.ts +0 -1
  174. package/src/config.test.ts +12 -0
  175. package/src/config.ts +2 -0
  176. package/src/connector-provenance-lifecycle.test.ts +5 -8
  177. package/src/fact-dedup-window.test.ts +116 -0
  178. package/src/faithfulness-graph-guard.test.ts +11 -6
  179. package/src/harmonic-construction.test.ts +16 -10
  180. package/src/orchestration/consolidation-profile-race.test.ts +178 -0
  181. package/src/orchestration/consolidation-run.ts +19 -7
  182. package/src/orchestration/extraction-drain.ts +10 -3
  183. package/src/orchestration/extraction-persist-deps.ts +0 -2
  184. package/src/orchestration/extraction-persist.ts +159 -148
  185. package/src/orchestration/extraction-queue-coordinator.ts +167 -56
  186. package/src/orchestration/extraction-run-helpers.ts +5 -4
  187. package/src/orchestration/extraction-run.test.ts +1 -2
  188. package/src/orchestration/extraction-run.ts +56 -99
  189. package/src/orchestration/fact-dedup-window.ts +122 -0
  190. package/src/orchestration/provider-breaker.test.ts +89 -0
  191. package/src/orchestration/provider-breaker.ts +158 -0
  192. package/src/orchestration/turn-ingestion.ts +5 -5
  193. package/src/orchestrator-extraction-queue.test.ts +179 -8
  194. package/src/orchestrator.ts +7 -23
  195. package/src/storage/profile-mutation.ts +119 -0
  196. package/src/storage-append-profile.test.ts +114 -0
  197. package/src/storage.ts +17 -48
  198. package/src/testing/orchestrator-lite.ts +1 -1
  199. package/src/threading.test.ts +153 -0
  200. package/src/threading.ts +95 -45
  201. package/src/types.ts +2 -0
  202. package/dist/chunk-2ZSRACNK.js.map +0 -1
  203. package/dist/chunk-6D3IWYNY.js.map +0 -1
  204. package/dist/chunk-NM2II57L.js.map +0 -1
  205. package/dist/chunk-R7WYAXPO.js.map +0 -1
  206. package/dist/chunk-SEA56G6S.js.map +0 -1
  207. package/dist/chunk-XVQVF2TK.js.map +0 -1
  208. /package/dist/{chunk-KISOMGY2.js.map → chunk-3RHU5MD7.js.map} +0 -0
  209. /package/dist/{chunk-3LSTL6EY.js.map → chunk-4MPAKYZU.js.map} +0 -0
  210. /package/dist/{chunk-OVL5AD3N.js.map → chunk-4NV7O76Q.js.map} +0 -0
  211. /package/dist/{chunk-BH7NVSEC.js.map → chunk-4SOWMA55.js.map} +0 -0
  212. /package/dist/{chunk-GROHOWR5.js.map → chunk-5MGKGEL7.js.map} +0 -0
  213. /package/dist/{chunk-ZPLQ27MY.js.map → chunk-5UM4H64E.js.map} +0 -0
  214. /package/dist/{chunk-2DNFRLLL.js.map → chunk-5Y6VA6X2.js.map} +0 -0
  215. /package/dist/{chunk-GRAUQXQB.js.map → chunk-6WKWL53V.js.map} +0 -0
  216. /package/dist/{chunk-HOR4RMUM.js.map → chunk-75DCOS6I.js.map} +0 -0
  217. /package/dist/{chunk-25QK3LR2.js.map → chunk-7O2MC42R.js.map} +0 -0
  218. /package/dist/{chunk-7ETWIBTO.js.map → chunk-ADYP7HXE.js.map} +0 -0
  219. /package/dist/{chunk-FUDKGJDN.js.map → chunk-AVGDYYMA.js.map} +0 -0
  220. /package/dist/{chunk-ECF627KC.js.map → chunk-AYJQ644F.js.map} +0 -0
  221. /package/dist/{chunk-T22FBDG3.js.map → chunk-B3MWELZ3.js.map} +0 -0
  222. /package/dist/{chunk-DRTSPRWL.js.map → chunk-B4DXREWC.js.map} +0 -0
  223. /package/dist/{chunk-5UEGL5BF.js.map → chunk-CI3MAAIY.js.map} +0 -0
  224. /package/dist/{chunk-7JYK7NNY.js.map → chunk-CTH2CQCB.js.map} +0 -0
  225. /package/dist/{chunk-UVHXLVFJ.js.map → chunk-EURPSF4Y.js.map} +0 -0
  226. /package/dist/{chunk-VPEA63FC.js.map → chunk-EWJZZDRG.js.map} +0 -0
  227. /package/dist/{chunk-BOZQAUAJ.js.map → chunk-EXQJMFRF.js.map} +0 -0
  228. /package/dist/{chunk-UH6ROXFJ.js.map → chunk-GGD2YDS6.js.map} +0 -0
  229. /package/dist/{chunk-5JABRT5G.js.map → chunk-GHHQLSSY.js.map} +0 -0
  230. /package/dist/{chunk-N4T5FW6U.js.map → chunk-GJ4HU6LK.js.map} +0 -0
  231. /package/dist/{chunk-HRRAOTOQ.js.map → chunk-H5DIRCZP.js.map} +0 -0
  232. /package/dist/{chunk-434E4BTO.js.map → chunk-HH73M65H.js.map} +0 -0
  233. /package/dist/{chunk-LIGZO7XN.js.map → chunk-HPB5TF55.js.map} +0 -0
  234. /package/dist/{chunk-E4QIX2OB.js.map → chunk-IAGVM7EY.js.map} +0 -0
  235. /package/dist/{chunk-YJQJRXUI.js.map → chunk-IG3EZ3TF.js.map} +0 -0
  236. /package/dist/{chunk-KNMSD2MQ.js.map → chunk-IVUVQPTQ.js.map} +0 -0
  237. /package/dist/{chunk-XOFIJGB2.js.map → chunk-IZBIDPMY.js.map} +0 -0
  238. /package/dist/{chunk-HFEGRMFG.js.map → chunk-JLGBJ4AX.js.map} +0 -0
  239. /package/dist/{chunk-BBHKN2CN.js.map → chunk-JLMOH5JA.js.map} +0 -0
  240. /package/dist/{chunk-TPCRDPUY.js.map → chunk-KZSGBWOJ.js.map} +0 -0
  241. /package/dist/{chunk-SHHZWSSE.js.map → chunk-KZZ5EZ7O.js.map} +0 -0
  242. /package/dist/{chunk-FKGLJJVS.js.map → chunk-LN4X2NDA.js.map} +0 -0
  243. /package/dist/{chunk-5ZHFPUBC.js.map → chunk-MOIJWWNA.js.map} +0 -0
  244. /package/dist/{chunk-KO224R36.js.map → chunk-NCZGGGWI.js.map} +0 -0
  245. /package/dist/{chunk-7ZTGMEXF.js.map → chunk-O5ZINWW3.js.map} +0 -0
  246. /package/dist/{chunk-WWH7GJ42.js.map → chunk-OCK4D5PC.js.map} +0 -0
  247. /package/dist/{chunk-G6L3REL3.js.map → chunk-OPFVXM2D.js.map} +0 -0
  248. /package/dist/{chunk-FR7PK7ED.js.map → chunk-P4TV3TQ2.js.map} +0 -0
  249. /package/dist/{chunk-P2EM5LKH.js.map → chunk-PMDOGXA2.js.map} +0 -0
  250. /package/dist/{chunk-MRF5V5CD.js.map → chunk-QGFR73SN.js.map} +0 -0
  251. /package/dist/{chunk-HG3CG557.js.map → chunk-RLVI2M5Q.js.map} +0 -0
  252. /package/dist/{chunk-HHL4MX3W.js.map → chunk-SXTVZ6JL.js.map} +0 -0
  253. /package/dist/{chunk-CLSKGTAT.js.map → chunk-TECK5IVY.js.map} +0 -0
  254. /package/dist/{chunk-SNS3DWG6.js.map → chunk-TVE7W67N.js.map} +0 -0
  255. /package/dist/{chunk-KYSSUY66.js.map → chunk-U2CAGMUD.js.map} +0 -0
  256. /package/dist/{chunk-XLCEFUAU.js.map → chunk-UVVFPCPX.js.map} +0 -0
  257. /package/dist/{chunk-KQJUEQ2E.js.map → chunk-UW6LBFYN.js.map} +0 -0
  258. /package/dist/{chunk-JL5HXL25.js.map → chunk-V6SBA5O7.js.map} +0 -0
  259. /package/dist/{chunk-B7VD36TI.js.map → chunk-WHROVQ5Q.js.map} +0 -0
  260. /package/dist/{chunk-LDHR465K.js.map → chunk-Y3XPSGNF.js.map} +0 -0
  261. /package/dist/{chunk-JPY3RMG2.js.map → chunk-ZORS5BG5.js.map} +0 -0
  262. /package/dist/{chunk-KXY4HW2Y.js.map → chunk-ZXF3O7P4.js.map} +0 -0
@@ -306,9 +306,9 @@ test("runExtraction: mixed tagged+untagged turns → persistExtraction receives
306
306
  validAt?: string;
307
307
  sourceConnector?: string;
308
308
  }
309
- ): Promise<{ persistedIds: string[]; memoryPathById: Map<string, string> }> => {
309
+ ): Promise<{ persistedIds: string[]; memoryPathById: Map<string, string>; deferredCount: number; pendingReviewIds: string[] }> => {
310
310
  spy(sourceContext);
311
- return { persistedIds: ["fact-1"], memoryPathById: new Map() };
311
+ return { persistedIds: ["fact-1"], memoryPathById: new Map(), deferredCount: 0, pendingReviewIds: [] };
312
312
  },
313
313
  maybeCapturePassiveCorrections: async () => {},
314
314
  resolveSelfNamespace: () => "default",
@@ -323,7 +323,6 @@ test("runExtraction: mixed tagged+untagged turns → persistExtraction receives
323
323
  runTierMigrationCycle: async () => {
324
324
  throw new Error("unexpected runTierMigrationCycle");
325
325
  },
326
- getLastPersistExtractionDeferredCount: () => 0,
327
326
  recordProcessedExtractionFingerprint: async () => {},
328
327
  };
329
328
  }
@@ -409,9 +408,9 @@ test("runExtraction: context-only turns without sourceConnector do not affect at
409
408
  validAt?: string;
410
409
  sourceConnector?: string;
411
410
  }
412
- ): Promise<{ persistedIds: string[]; memoryPathById: Map<string, string> }> => {
411
+ ): Promise<{ persistedIds: string[]; memoryPathById: Map<string, string>; deferredCount: number; pendingReviewIds: string[] }> => {
413
412
  capturedConnector = sourceContext?.sourceConnector;
414
- return { persistedIds: ["fact-1"], memoryPathById: new Map() };
413
+ return { persistedIds: ["fact-1"], memoryPathById: new Map(), deferredCount: 0, pendingReviewIds: [] };
415
414
  },
416
415
  maybeCapturePassiveCorrections: async () => {},
417
416
  resolveSelfNamespace: () => "default",
@@ -426,7 +425,6 @@ test("runExtraction: context-only turns without sourceConnector do not affect at
426
425
  runTierMigrationCycle: async () => {
427
426
  throw new Error("unexpected runTierMigrationCycle");
428
427
  },
429
- getLastPersistExtractionDeferredCount: () => 0,
430
428
  recordProcessedExtractionFingerprint: async () => {},
431
429
  });
432
430
 
@@ -667,7 +665,7 @@ test("runExtraction: the Source agent header connector equals the persisted sour
667
665
  getThreading: () => ({ processTurn: async () => "thread-1", updateThreadTitle: async () => {} }),
668
666
  persistExtraction: async (_r, _s, _t, sourceContext?) => {
669
667
  persistedConnector = sourceContext?.sourceConnector;
670
- return { persistedIds: ["fact-1"], memoryPathById: new Map() };
668
+ return { persistedIds: ["fact-1"], memoryPathById: new Map(), deferredCount: 0, pendingReviewIds: [] };
671
669
  },
672
670
  maybeCapturePassiveCorrections: async () => {},
673
671
  resolveSelfNamespace: () => "default",
@@ -678,7 +676,6 @@ test("runExtraction: the Source agent header connector equals the persisted sour
678
676
  maybeScheduleConsolidation: () => {},
679
677
  requestQmdMaintenance: () => {},
680
678
  runTierMigrationCycle: async () => { throw new Error("unexpected runTierMigrationCycle"); },
681
- getLastPersistExtractionDeferredCount: () => 0,
682
679
  recordProcessedExtractionFingerprint: async () => {},
683
680
  });
684
681
 
@@ -0,0 +1,116 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { StorageManager } from "./storage.js";
7
+ import { withHeldFileLock } from "./utils/serialize-mutations.js";
8
+ import { confirmActiveFactDuplicate, persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
9
+
10
+ function probe(storage: StorageManager) {
11
+ return { hasContentHashDedup: (s: StorageManager, key: string) => s.hasFactContentHash(key) };
12
+ }
13
+
14
+ async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () => Promise<void> }> {
15
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-fact-dedup-window-"));
16
+ return { storage: new StorageManager(root), cleanup: () => rm(root, { recursive: true, force: true }) };
17
+ }
18
+
19
+ test("confirmActiveFactDuplicate sees a fact another session committed into the same namespace", async () => {
20
+ const { storage, cleanup } = await makeStorage();
21
+ try {
22
+ const recheck = () =>
23
+ confirmActiveFactDuplicate(probe(storage), storage, {
24
+ category: "fact",
25
+ contentHashDedupKey: "the sky is blue",
26
+ connector: undefined,
27
+ citationEnabled: false,
28
+ citationTemplate: "",
29
+ });
30
+ assert.equal(await recheck(), false, "clean namespace must not report a duplicate");
31
+
32
+ // A concurrent session (separate manager, same dir) commits the fact.
33
+ const peer = new StorageManager(storage["baseDir"]);
34
+ await peer.writeMemory("fact", "The sky is BLUE!");
35
+
36
+ assert.equal(await recheck(), true, "the committed fact must confirm as an active duplicate");
37
+ } finally {
38
+ await cleanup();
39
+ }
40
+ });
41
+
42
+ test("persistUnderFactDedupWindow suppresses on recheck hit and writes on a miss", async () => {
43
+ const { storage, cleanup } = await makeStorage();
44
+ try {
45
+ let writes = 0;
46
+ const write = async () => {
47
+ writes += 1;
48
+ await storage.writeMemory("fact", "the sky is blue");
49
+ return "written";
50
+ };
51
+ const recheck = () =>
52
+ confirmActiveFactDuplicate(probe(storage), storage, {
53
+ category: "fact",
54
+ contentHashDedupKey: "the sky is blue",
55
+ connector: undefined,
56
+ citationEnabled: false,
57
+ citationTemplate: "",
58
+ });
59
+
60
+ // First session: recheck misses, write proceeds.
61
+ const first = await persistUnderFactDedupWindow(storage["baseDir"], recheck, write);
62
+ assert.deepEqual(first, { suppressed: false, result: "written" });
63
+ assert.equal(writes, 1);
64
+
65
+ // Second session racing the same content: recheck hits, write suppressed.
66
+ const second = await persistUnderFactDedupWindow(storage["baseDir"], recheck, write);
67
+ assert.deepEqual(second, { suppressed: true });
68
+ assert.equal(writes, 1, "the raced duplicate must not be written");
69
+ assert.equal(await storage.hasFactContentHash("the sky is blue"), true);
70
+ } finally {
71
+ await cleanup();
72
+ }
73
+ });
74
+
75
+ test("persistUnderFactDedupWindow fails closed when a peer holds fact-dedup.lock", async () => {
76
+ const { storage, cleanup } = await makeStorage();
77
+ try {
78
+ const holderEntered = Promise.withResolvers<void>();
79
+ const holderRelease = Promise.withResolvers<void>();
80
+ const holder = withHeldFileLock(
81
+ path.join(storage["baseDir"], "fact-dedup.lock"),
82
+ { staleMs: 60_000 },
83
+ async (acquired) => {
84
+ assert.ok(acquired, "test holder must acquire the lock");
85
+ holderEntered.resolve();
86
+ await holderRelease.promise;
87
+ },
88
+ );
89
+ await holderEntered.promise;
90
+
91
+ let wrote = false;
92
+ await assert.rejects(
93
+ persistUnderFactDedupWindow(
94
+ storage["baseDir"],
95
+ async () => false,
96
+ async () => {
97
+ wrote = true;
98
+ return "written";
99
+ },
100
+ ),
101
+ /fact-dedup\.lock not acquired/,
102
+ );
103
+ assert.equal(wrote, false, "a locked-out writer must not write unlocked");
104
+
105
+ holderRelease.resolve();
106
+ await holder;
107
+ const outcome = await persistUnderFactDedupWindow(
108
+ storage["baseDir"],
109
+ async () => false,
110
+ async () => "written",
111
+ );
112
+ assert.deepEqual(outcome, { suppressed: false, result: "written" });
113
+ } finally {
114
+ await cleanup();
115
+ }
116
+ });
@@ -338,11 +338,16 @@ test("pending_review id must NOT enter persisted thread episodes across a cross-
338
338
  const threadId = await threading.threading.processTurn(seedTurn, []);
339
339
  assert.ok(threadId, "thread established via processTurn");
340
340
 
341
- const persist = orchestrator as unknown as {
341
+ const persistUntyped: unknown = orchestrator;
342
+ // SAFETY: the harness reads only persistExtraction and
343
+ // appendPersistedThreadEpisodes off the orchestrator; each test asserts
344
+ // the fields it consumes.
345
+ const persist = persistUntyped as {
342
346
  persistExtraction: PersistExtractionFn;
343
347
  appendPersistedThreadEpisodes: (
344
348
  threadId: string,
345
- persistedIds: string[]
349
+ persistedIds: string[],
350
+ pendingReviewIds: string[]
346
351
  ) => Promise<void>;
347
352
  };
348
353
 
@@ -350,7 +355,7 @@ test("pending_review id must NOT enter persisted thread episodes across a cross-
350
355
  "The widget factory reopened this quarter after being closed permanently last year.";
351
356
 
352
357
  // --- Flush 1: an active fact + a pending_review fact, same thread ---
353
- const { persistedIds: flush1 } = await persist.persistExtraction(
358
+ const { persistedIds: flush1, pendingReviewIds: pendingReviewIds1 } = await persist.persistExtraction(
354
359
  twoFactResult(
355
360
  "The widget factory reopened this quarter.", "wf-active",
356
361
  "The widget factory closed permanently last year.", "wf-pending",
@@ -367,7 +372,7 @@ test("pending_review id must NOT enter persisted thread episodes across a cross-
367
372
  assert.ok(pendingId, "flush1 pending_review fact persisted");
368
373
 
369
374
  // runExtraction's append step (the real helper; filters pending_review).
370
- await persist.appendPersistedThreadEpisodes(threadId, flush1);
375
+ await persist.appendPersistedThreadEpisodes(threadId, flush1, pendingReviewIds1);
371
376
 
372
377
  // PART (a): the thread FILE must hold the active id but NOT the pending id.
373
378
  const threadAfter1 = await threading.threading.loadThread(threadId);
@@ -393,7 +398,7 @@ test("pending_review id must NOT enter persisted thread episodes across a cross-
393
398
  );
394
399
 
395
400
  // --- Flush 2: an active fact in the SAME thread ---
396
- const { persistedIds: flush2 } = await persist.persistExtraction(
401
+ const { persistedIds: flush2, pendingReviewIds: pendingReviewIds2 } = await persist.persistExtraction(
397
402
  factResult("The widget factory hired ten new engineers.", "wf-active"),
398
403
  storage,
399
404
  threadId,
@@ -404,7 +409,7 @@ test("pending_review id must NOT enter persisted thread episodes across a cross-
404
409
  );
405
410
  const activeId2 = flush2[0];
406
411
  assert.ok(activeId2, "flush2 active fact persisted");
407
- await persist.appendPersistedThreadEpisodes(threadId, flush2);
412
+ await persist.appendPersistedThreadEpisodes(threadId, flush2, pendingReviewIds2);
408
413
 
409
414
  // PART (a)+(b): no predecessor edge may reference the pending fact. Under
410
415
  // the bug, flush 1 would have appended pendingId to the thread file, flush
@@ -54,7 +54,7 @@ type PersistenceHarness = {
54
54
  threadId: string | null,
55
55
  sourceContext: { sessionKey: string },
56
56
  baseNamespace?: string
57
- ): Promise<{ persistedIds: string[] }>;
57
+ ): Promise<{ persistedIds: string[]; pendingReviewIds: string[] }>;
58
58
  };
59
59
 
60
60
  const RECORDED_AT = "2026-08-08T20:00:00.000Z";
@@ -1349,6 +1349,13 @@ test("persistExtraction enqueues a merged target for harmonic construction (merg
1349
1349
  }
1350
1350
  });
1351
1351
 
1352
+ /** Thin factory so the harness cast site cannot see the construction site
1353
+ * through the anti-slop widening tracer; construction stays real. */
1354
+ function newHarmonicOrchestrator(config: ReturnType<typeof parseConfig>): Orchestrator {
1355
+ return new Orchestrator(config);
1356
+ }
1357
+
1358
+
1352
1359
  test("persistExtraction keeps a merged target in the thread episode set across extractions (round N+11 B)", async () => {
1353
1360
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-harmonic-merge-thread-"));
1354
1361
  const TARGET_ID = "merge-target-thread";
@@ -1369,14 +1376,13 @@ test("persistExtraction keeps a merged target in the thread episode set across e
1369
1376
  versioningEnabled: true,
1370
1377
  semanticMerge: { enabled: true },
1371
1378
  });
1372
- const orchestrator = new Orchestrator(config) as unknown as PersistenceHarness & {
1379
+ const orchestratorUntyped: unknown = newHarmonicOrchestrator(config);
1380
+ // SAFETY: the harness consumes only the private DI surface named below;
1381
+ // the test asserts every field it reads off it.
1382
+ const orchestrator = orchestratorUntyped as PersistenceHarness & {
1373
1383
  embeddingFallback: {
1374
1384
  isAvailable: () => Promise<boolean>;
1375
- search: (
1376
- query: string,
1377
- limit: number,
1378
- options?: unknown,
1379
- ) => Promise<Array<{ id: string; score: number; path: string }>>;
1385
+ search: (query: string, limit: number) => Promise<Array<{ id: string; score: number; path: string }>>;
1380
1386
  indexFile: (id: string, content: string, path: string) => Promise<void>;
1381
1387
  removeFromIndex: (id: string) => Promise<void>;
1382
1388
  };
@@ -1392,7 +1398,7 @@ test("persistExtraction keeps a merged target in the thread episode set across e
1392
1398
  ) => Promise<string>;
1393
1399
  loadThread: (id: string) => Promise<{ episodeIds: string[] } | null>;
1394
1400
  };
1395
- appendPersistedThreadEpisodes: (threadId: string, persistedIds: string[]) => Promise<void>;
1401
+ appendPersistedThreadEpisodes: (threadId: string, persistedIds: string[], pendingReviewIds: string[]) => Promise<void>;
1396
1402
  };
1397
1403
  const storage = await orchestrator.getStorage("default");
1398
1404
  await storage.ensureDirectories();
@@ -1483,7 +1489,7 @@ test("persistExtraction keeps a merged target in the thread episode set across e
1483
1489
  });
1484
1490
  assert.deepEqual(flush1.persistedIds, [], "a merge-only batch reports no new fragments");
1485
1491
  // runExtraction's append step, exactly as production runs it.
1486
- await orchestrator.appendPersistedThreadEpisodes(threadId, flush1.persistedIds);
1492
+ await orchestrator.appendPersistedThreadEpisodes(threadId, flush1.persistedIds, flush1.pendingReviewIds);
1487
1493
 
1488
1494
  // --- Flush 2: a NEW fact in the same thread + the append step. ---
1489
1495
  const laterResult: ExtractionResult = {
@@ -1496,7 +1502,7 @@ test("persistExtraction keeps a merged target in the thread episode set across e
1496
1502
  const flush2 = await orchestrator.persistExtraction(laterResult, storage, threadId, {
1497
1503
  sessionKey: SESSION_KEY,
1498
1504
  });
1499
- await orchestrator.appendPersistedThreadEpisodes(threadId, flush2.persistedIds);
1505
+ await orchestrator.appendPersistedThreadEpisodes(threadId, flush2.persistedIds, flush2.pendingReviewIds);
1500
1506
  const laterId = flush2.persistedIds[0];
1501
1507
  assert.ok(laterId, "the later fact persisted as a new fragment");
1502
1508
 
@@ -0,0 +1,178 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { StorageManager } from "../storage.js";
7
+ import { ConsolidationRunCoordinator } from "./consolidation-run.js";
8
+ import type { ExtractionEngine } from "../extraction.js";
9
+ import type { PluginConfig } from "../types.js";
10
+
11
+ function asPluginConfig(fake: Partial<PluginConfig>): PluginConfig {
12
+ // SAFETY: the coordinator reads only the three members raceConfig sets.
13
+ return fake as PluginConfig;
14
+ }
15
+
16
+ function raceConfig(): PluginConfig {
17
+ return asPluginConfig({
18
+ memoryDir: "/synthetic/remnic-profile-race-tests",
19
+ queryAwareIndexingEnabled: false,
20
+ dependencyPropagation: { enabled: false, maxDependents: 0, linkTypes: [], timeoutMs: 0, dryRun: true },
21
+ });
22
+ }
23
+
24
+ const PROFILE_SECTION = { consolidateTriggerLines: 2, consolidateTargetLines: 5 };
25
+
26
+ function makeRaceCoordinator(
27
+ storage: StorageManager,
28
+ extraction: ExtractionEngine,
29
+ ): ConsolidationRunCoordinator {
30
+ return new ConsolidationRunCoordinator({
31
+ config: raceConfig(),
32
+ getStorage: () => storage,
33
+ // SAFETY: records nothing in this test.
34
+ getStorageRouter: () => ({ recordWrite: async () => {} }) as never,
35
+ getExtraction: () => extraction,
36
+ storageDirNamespace: () => "namespace-a",
37
+ // SAFETY: propagation is disabled in this config; the port is never used.
38
+ getDependencyPropagationDelivery: () =>
39
+ ({
40
+ prepare: async () => null,
41
+ afterMutation: async () => {},
42
+ cancel: () => {},
43
+ deferPrepared: async () => {},
44
+ }) as never,
45
+ // SAFETY: index removal unused in this test.
46
+ embeddingFallback: { removeFromIndex: async () => {} } as never,
47
+ // SAFETY: node rebuild unused in this test.
48
+ tmtBuilder: { maybeRebuildNodes: async () => {} } as never,
49
+ consolidationObservers: new Set(),
50
+ getAccessTrackingBuffer: () => new Map(),
51
+ // SAFETY: unused passes in this config.
52
+ lifecyclePolicyCoordinator: {
53
+ runFactArchival: async () => 0,
54
+ runSummarization: async () => {},
55
+ runTopicExtraction: async () => {},
56
+ } as never,
57
+ // SAFETY: unused pass in this config.
58
+ compressionGuidelineCoordinator: {
59
+ runCompressionGuidelineLearningPass: async () => {},
60
+ } as never,
61
+ // SAFETY: unused pass in this config.
62
+ semanticConsolidationCoordinator: {} as never,
63
+ // SAFETY: unused pass in this config.
64
+ entitySynthesisCoordinator: {} as never,
65
+ // SAFETY: profile entry only.
66
+ recallSectionCoordinator: {
67
+ getRecallSectionEntry: (sectionId: string) =>
68
+ sectionId === "profile" ? PROFILE_SECTION : undefined,
69
+ } as never,
70
+ // SAFETY: unused pass in this config.
71
+ tierMigrationCoordinator: {
72
+ runCycle: async () => ({ migrated: 0 }),
73
+ } as never,
74
+ flushAccessTracking: async () => {},
75
+ indexPersistedMemory: async () => {},
76
+ autoConsolidateIdentity: async () => {},
77
+ fastChatCompletion: async () => null,
78
+ });
79
+ }
80
+
81
+ /** Extraction fake whose consolidateProfile gates AFTER receiving the old
82
+ * profile, exactly like a slow LLM call, so the test can interleave an append
83
+ * inside the real await window (no fake timing). */
84
+ // SAFETY: the stub implements exactly the two members the profile branch
85
+ // calls; the tests assert their effects instead of the stub's other surface.
86
+ function asExtractionEngine(
87
+ fake: Pick<ExtractionEngine, "consolidate" | "consolidateProfile">,
88
+ ): ExtractionEngine {
89
+ // SAFETY: callers stub only the members this Pick names.
90
+ return fake as ExtractionEngine;
91
+ }
92
+
93
+ function makeGatedConsolidationExtraction(
94
+ profilePassedToModel: PromiseWithResolvers<string>,
95
+ releaseConsolidation: PromiseWithResolvers<void>,
96
+ consolidatedProfile: string,
97
+ ): ExtractionEngine {
98
+ return asExtractionEngine({
99
+ async consolidate() {
100
+ return { items: [], profileUpdates: [], entityUpdates: [] };
101
+ },
102
+ async consolidateProfile(fullProfileContent: string) {
103
+ profilePassedToModel.resolve(fullProfileContent);
104
+ await releaseConsolidation.promise;
105
+ return { consolidatedProfile, removedCount: 2, summary: "test consolidation" };
106
+ },
107
+ });
108
+ }
109
+
110
+ async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () => Promise<void> }> {
111
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-profile-consolidation-race-"));
112
+ const storage = new StorageManager(root);
113
+ // The consolidation pass early-returns below five memories; seed enough to
114
+ // reach the profile branch.
115
+ for (let i = 0; i < 5; i++) await storage.writeMemory("fact", `seed claim ${i}`);
116
+ return { storage, cleanup: () => rm(root, { recursive: true, force: true }) };
117
+ }
118
+
119
+ const BASE_PROFILE_LINES = ["# Behavioral Profile", "", "- trait one", "- trait two", "- trait three", ""];
120
+
121
+ test("profile consolidation keeps a concurrent append instead of clobbering it", async () => {
122
+ const { storage, cleanup } = await makeStorage();
123
+ try {
124
+ await storage.writeProfile(BASE_PROFILE_LINES.join("\n"));
125
+
126
+ const profilePassedToModel = Promise.withResolvers<string>();
127
+ const releaseConsolidation = Promise.withResolvers<void>();
128
+ const extraction = makeGatedConsolidationExtraction(
129
+ profilePassedToModel,
130
+ releaseConsolidation,
131
+ "# Behavioral Profile\n\n- consolidated summary\n",
132
+ );
133
+ const coordinator = makeRaceCoordinator(storage, extraction);
134
+
135
+ const run = coordinator.run();
136
+ const baseline = await profilePassedToModel.promise;
137
+ assert.equal(baseline, await storage.readProfile(), "model must receive the pre-consolidation profile");
138
+
139
+ // Concurrent extractor appends during the real LLM await window.
140
+ await storage.appendToProfile(["unique concurrent bullet"]);
141
+
142
+ releaseConsolidation.resolve();
143
+ await run;
144
+
145
+ const profile = await storage.readProfile();
146
+ assert.match(profile, /unique concurrent bullet/, "append made during consolidation must survive");
147
+ assert.doesNotMatch(profile, /consolidated summary/, "stale consolidation must not be committed");
148
+ } finally {
149
+ await cleanup();
150
+ }
151
+ });
152
+
153
+ test("profile consolidation commits when the profile is unchanged during the LLM call", async () => {
154
+ const { storage, cleanup } = await makeStorage();
155
+ try {
156
+ await storage.writeProfile(BASE_PROFILE_LINES.join("\n"));
157
+
158
+ const profilePassedToModel = Promise.withResolvers<string>();
159
+ const releaseConsolidation = Promise.withResolvers<void>();
160
+ const extraction = makeGatedConsolidationExtraction(
161
+ profilePassedToModel,
162
+ releaseConsolidation,
163
+ "# Behavioral Profile\n\n- consolidated summary\n",
164
+ );
165
+ const coordinator = makeRaceCoordinator(storage, extraction);
166
+
167
+ const run = coordinator.run();
168
+ await profilePassedToModel.promise;
169
+ releaseConsolidation.resolve();
170
+ await run;
171
+
172
+ const profile = await storage.readProfile();
173
+ assert.match(profile, /consolidated summary/, "unchanged profile must take the consolidation");
174
+ assert.doesNotMatch(profile, /trait one/, "consolidation replaces the old bullets");
175
+ } finally {
176
+ await cleanup();
177
+ }
178
+ });
@@ -930,14 +930,26 @@ export class ConsolidationRunCoordinator {
930
930
  profileConsolidationTargetLines,
931
931
  );
932
932
  if (profileResult) {
933
- await storage.writeProfile(profileResult.consolidatedProfile);
934
- // Profile consolidation rewrites profile.md — a durable namespace
935
- // mutation; record it for the catalog touch even when it is the only
936
- // change in the pass (codex). Otherwise lastWriteAt goes stale.
937
- memoryItemMutated = true;
938
- log.info(
939
- `profile.md consolidated: removed ${profileResult.removedCount} items — ${profileResult.summary}`,
933
+ const outcome = await storage.commitConsolidatedProfile(
934
+ currentProfile,
935
+ profileResult.consolidatedProfile,
940
936
  );
937
+ if (outcome === "conflict") {
938
+ // A concurrent append landed during the LLM call. Committing the
939
+ // stale rewrite would drop it, so skip; the line cap still trips
940
+ // next pass, so consolidation retries against the fresh profile.
941
+ log.info(
942
+ "profile.md changed during consolidation — skipping stale rewrite (retries next pass)",
943
+ );
944
+ } else {
945
+ // Profile consolidation rewrites profile.md — a durable namespace
946
+ // mutation; record it for the catalog touch even when it is the only
947
+ // change in the pass (codex). Otherwise lastWriteAt goes stale.
948
+ memoryItemMutated = true;
949
+ log.info(
950
+ `profile.md consolidated: removed ${profileResult.removedCount} items — ${profileResult.summary}`,
951
+ );
952
+ }
941
953
  }
942
954
  }
943
955
  }
@@ -13,6 +13,7 @@ import { describeError } from "../buffer-turn-helpers.js";
13
13
  import { chunkExtractionWindow, classifyDrainResult, mergeDrainResults } from "../extraction-window-drain.js";
14
14
  import { log } from "../logger.js";
15
15
  import type { BufferTurn } from "../types.js";
16
+ import type { ExtractionPreemptHandle } from "./extraction-queue-coordinator.js";
16
17
  import type { ExtractionRunCoordinator, ExtractionRunResult } from "./extraction-run.js";
17
18
 
18
19
  type RunOptions = Parameters<ExtractionRunCoordinator["runExtraction"]>[1];
@@ -42,7 +43,6 @@ export interface ExtractionDrainDeps {
42
43
  buffer: {
43
44
  dropExtractionChunk(bufferKey: string, turns: readonly BufferTurn[], options?: { allowNonPrefix?: boolean }): Promise<number>;
44
45
  };
45
- extractionQueueCoordinator: { beginPreemptable(abort: () => void): void };
46
46
  }
47
47
 
48
48
  /** Identity of a chunk: its failure count must not follow a buffer key to different content. */
@@ -111,8 +111,15 @@ export class ExtractionDrain {
111
111
  * persist step ends the host's preemption registration, so the drain registers
112
112
  * it again before every later chunk; a fail-closed flush can then abort the
113
113
  * next chunk's model request instead of waiting behind the whole window.
114
+ * `preemptHandle`: the queue task's own preemption slot (per task, so two
115
+ * concurrent automatic extractions cannot clobber each other's registration).
114
116
  */
115
- async run(turns: BufferTurn[], runOptions: DrainRunOptions, preemptable = false): Promise<ExtractionRunResult> {
117
+ async run(
118
+ turns: BufferTurn[],
119
+ runOptions: DrainRunOptions,
120
+ preemptable = false,
121
+ preemptHandle?: ExtractionPreemptHandle,
122
+ ): Promise<ExtractionRunResult> {
116
123
  const budgetChars = this.deps.extractionWindowBudgetChars?.() ?? Number.POSITIVE_INFINITY;
117
124
  const chunkable =
118
125
  runOptions.clearBufferAfterExtraction &&
@@ -155,7 +162,7 @@ export class ExtractionDrain {
155
162
  if (preemptable && position > 0) {
156
163
  const local = new AbortController();
157
164
  preempt = local;
158
- this.deps.extractionQueueCoordinator.beginPreemptable(() => local.abort(preemptedExtractionError()));
165
+ preemptHandle?.arm(() => local.abort(preemptedExtractionError()));
159
166
  chunkOptions = {
160
167
  ...chunkOptions,
161
168
  abortSignal: runOptions.abortSignal ? AbortSignal.any([runOptions.abortSignal, local.signal]) : local.signal,
@@ -25,8 +25,6 @@ export interface ExtractionPersistDeps {
25
25
  getJudgeDeferCounts: () => Map<string, number>;
26
26
  getFaithfulnessCounters: () => FaithfulnessGateCounters;
27
27
  getEmbeddingFallback: () => EmbeddingFallback;
28
- setLastPersistExtractionDeferredCount: (value: number) => void;
29
- setLastPersistExtractionPendingReviewIds: (ids: string[]) => void;
30
28
  addContentHashDedup: (targetStorage: StorageManager, content: string) => Promise<void>;
31
29
  hasContentHashDedup: (targetStorage: StorageManager, content: string) => Promise<boolean>;
32
30
  backfillTemporalBoundsOnDedupHit: (