@remnic/core 10.0.39 → 10.0.40

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 (190) hide show
  1. package/dist/access-admin-ops-surface.js +12 -12
  2. package/dist/access-authorization-probe.js +13 -13
  3. package/dist/access-boundary.js +12 -12
  4. package/dist/access-cli.js +50 -50
  5. package/dist/access-extraction-force-flush.js +12 -12
  6. package/dist/access-http-briefing.js +13 -13
  7. package/dist/access-http-query.js +14 -14
  8. package/dist/access-http.js +28 -28
  9. package/dist/access-identity-continuity-surface.js +12 -12
  10. package/dist/access-lcm-surface.js +12 -12
  11. package/dist/access-mcp.js +21 -21
  12. package/dist/access-namespace-preflight.js +12 -12
  13. package/dist/access-observe-write-surface.js +12 -12
  14. package/dist/access-operations-batch.js +13 -13
  15. package/dist/access-operations.js +20 -20
  16. package/dist/access-recall-concurrency.js +12 -12
  17. package/dist/access-recall-response.js +12 -12
  18. package/dist/access-recall-surface.js +12 -12
  19. package/dist/access-service.d.ts +2 -2
  20. package/dist/access-service.d.ts.map +1 -1
  21. package/dist/access-service.js +12 -12
  22. package/dist/access-surface-catalog.js +14 -14
  23. package/dist/active-recall.js +2 -2
  24. package/dist/briefing.js +3 -3
  25. package/dist/causal-consolidation.js +4 -4
  26. package/dist/{chunk-NJ2DZYEK.js → chunk-2T6KEOUL.js} +2 -2
  27. package/dist/{chunk-BUKZ7MTN.js → chunk-5TRU6QQC.js} +2 -2
  28. package/dist/{chunk-SRPGA6WN.js → chunk-7BCYAJKP.js} +2 -2
  29. package/dist/{chunk-JRTKYFFY.js → chunk-AY6LO7GD.js} +2 -2
  30. package/dist/{chunk-5KZNWERU.js → chunk-BIYVUPC6.js} +19 -3
  31. package/dist/{chunk-5KZNWERU.js.map → chunk-BIYVUPC6.js.map} +1 -1
  32. package/dist/{chunk-V2XMG7GM.js → chunk-CHNZPOWX.js} +3 -2
  33. package/dist/{chunk-V2XMG7GM.js.map → chunk-CHNZPOWX.js.map} +1 -1
  34. package/dist/{chunk-GPVGAHXL.js → chunk-CNAL35D5.js} +2 -2
  35. package/dist/{chunk-CR72WZ3M.js → chunk-EPEDMEHO.js} +4 -4
  36. package/dist/{chunk-CR72WZ3M.js.map → chunk-EPEDMEHO.js.map} +1 -1
  37. package/dist/{chunk-U66CTIF5.js → chunk-F2RDRE7M.js} +2 -2
  38. package/dist/{chunk-B7VKRCAG.js → chunk-FNEYSTB6.js} +2 -2
  39. package/dist/{chunk-GSBSGT43.js → chunk-FPHU6KVI.js} +2 -2
  40. package/dist/{chunk-RO2NNWR5.js → chunk-GAUS4XGN.js} +3 -3
  41. package/dist/{chunk-4HJ2QK32.js → chunk-GDOI4U4D.js} +2 -2
  42. package/dist/{chunk-IZRNMXZC.js → chunk-GRN4BVPM.js} +2 -2
  43. package/dist/{chunk-VCOCOBUS.js → chunk-GZMHBRIR.js} +3 -3
  44. package/dist/{chunk-TTQYX7VU.js → chunk-HN35OH2C.js} +2 -2
  45. package/dist/{chunk-F2IU6A2L.js → chunk-HSNV4CSW.js} +16 -16
  46. package/dist/{chunk-EKR6OO4P.js → chunk-I7CWAJHX.js} +2 -2
  47. package/dist/{chunk-LJXBFVI2.js → chunk-ICTJOOWZ.js} +2 -2
  48. package/dist/{chunk-SJHFU52W.js → chunk-IF6EG22P.js} +5 -5
  49. package/dist/chunk-IF6EG22P.js.map +1 -0
  50. package/dist/{chunk-IAOW2L6K.js → chunk-IJJWM2ZA.js} +2 -2
  51. package/dist/{chunk-6Z2ZWBOV.js → chunk-IWDFHVPJ.js} +2 -2
  52. package/dist/{chunk-GCW7UT5I.js → chunk-JJSMSS45.js} +2 -2
  53. package/dist/{chunk-O2O4FDAH.js → chunk-JXQTECMU.js} +2 -2
  54. package/dist/{chunk-PJRKLB4Z.js → chunk-L5PM7B3I.js} +16 -2
  55. package/dist/{chunk-PJRKLB4Z.js.map → chunk-L5PM7B3I.js.map} +1 -1
  56. package/dist/{chunk-ZXFKG7VZ.js → chunk-LB6HIXQC.js} +2 -2
  57. package/dist/{chunk-B3I2QQU7.js → chunk-LWIU5BMA.js} +3 -3
  58. package/dist/{chunk-WQYS47G4.js → chunk-N3PUGD7E.js} +2 -2
  59. package/dist/{chunk-JORSMFL6.js → chunk-NBEM5QEH.js} +5 -5
  60. package/dist/{chunk-VIS23B6O.js → chunk-NFJWB27H.js} +14 -12
  61. package/dist/chunk-NFJWB27H.js.map +1 -0
  62. package/dist/{chunk-JQCP5QW3.js → chunk-O67H7OXJ.js} +2 -2
  63. package/dist/{chunk-4LLV2N3R.js → chunk-ONIVKGNC.js} +2 -2
  64. package/dist/{chunk-WT2VVYBM.js → chunk-PJXSBXEC.js} +2 -2
  65. package/dist/{chunk-ANV373S5.js → chunk-PW63SXFM.js} +2 -2
  66. package/dist/{chunk-RIAKNXZB.js → chunk-Q77SLBWT.js} +3 -3
  67. package/dist/{chunk-7IANFJ24.js → chunk-QFD7OANX.js} +15 -15
  68. package/dist/chunk-QFD7OANX.js.map +1 -0
  69. package/dist/{chunk-22YJQTPN.js → chunk-QGNZ5QPV.js} +2 -2
  70. package/dist/{chunk-GE5ZNDLE.js → chunk-R42VUN5V.js} +3 -3
  71. package/dist/{chunk-MD3W2H3G.js → chunk-RPHCSMTQ.js} +31 -31
  72. package/dist/{chunk-MD3W2H3G.js.map → chunk-RPHCSMTQ.js.map} +1 -1
  73. package/dist/{chunk-LPD6I2U6.js → chunk-SLVR3U4E.js} +2 -2
  74. package/dist/{chunk-JZFJZLJY.js → chunk-UGV256H3.js} +2 -2
  75. package/dist/{chunk-L74NWAD4.js → chunk-UKIIAEJF.js} +2 -2
  76. package/dist/{chunk-5Q6CR65T.js → chunk-VTZSIG6O.js} +2 -2
  77. package/dist/{chunk-4UT2NUAC.js → chunk-WJMAWDAU.js} +2 -2
  78. package/dist/{chunk-JT3IXJIL.js → chunk-XBHUMPIQ.js} +7 -7
  79. package/dist/{chunk-JM6U3SAE.js → chunk-XJ77IOAD.js} +2 -2
  80. package/dist/{chunk-RWW5KPIY.js → chunk-XLUMO5H2.js} +12 -12
  81. package/dist/chunk-XLUMO5H2.js.map +1 -0
  82. package/dist/{chunk-ODJ2HRFW.js → chunk-XXBCUIYQ.js} +7 -7
  83. package/dist/{chunk-JOOGPZJE.js → chunk-XXW3P3RX.js} +2 -2
  84. package/dist/{chunk-P3YUEPSQ.js → chunk-Y473XFTD.js} +2 -2
  85. package/dist/cli.js +38 -38
  86. package/dist/coding/export-okf-codegraph.js +2 -2
  87. package/dist/compounding/engine.js +4 -4
  88. package/dist/config.js +2 -2
  89. package/dist/connectors/codex-materialize-runner.js +3 -3
  90. package/dist/connectors/index.js +3 -3
  91. package/dist/convergence-refresh.js +5 -5
  92. package/dist/corpus-watermark.js +5 -5
  93. package/dist/entity-retrieval.js +3 -3
  94. package/dist/external-wiki-access.js +13 -13
  95. package/dist/extraction-error-classification.d.ts +15 -0
  96. package/dist/extraction-error-classification.d.ts.map +1 -1
  97. package/dist/extraction-error-classification.js +7 -3
  98. package/dist/extraction.js +5 -5
  99. package/dist/harmonic-retrieval.js +3 -3
  100. package/dist/index.js +50 -50
  101. package/dist/location/index.js +5 -5
  102. package/dist/maintenance/memory-governance.js +3 -3
  103. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  104. package/dist/maintenance/rebuild-memory-projection-runner.js +5 -5
  105. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  106. package/dist/memory-browse-operations.js +13 -13
  107. package/dist/namespaces/migrate.js +4 -4
  108. package/dist/namespaces/search.d.ts +1 -0
  109. package/dist/namespaces/search.d.ts.map +1 -1
  110. package/dist/namespaces/search.js +5 -3
  111. package/dist/namespaces/storage.js +3 -3
  112. package/dist/operator-doctor-corpus.js +6 -6
  113. package/dist/operator-toolkit.js +11 -11
  114. package/dist/orchestration/maintenance.js +13 -13
  115. package/dist/orchestration/qmd-result-resolver.d.ts +5 -0
  116. package/dist/orchestration/qmd-result-resolver.d.ts.map +1 -1
  117. package/dist/orchestration/recall-search-pipeline.d.ts.map +1 -1
  118. package/dist/orchestrator.js +50 -50
  119. package/dist/recall-navigation-operations.js +13 -13
  120. package/dist/recall-navigation.js +12 -12
  121. package/dist/resume-bundles.js +2 -2
  122. package/dist/semantic-consolidation.js +4 -4
  123. package/dist/semantic-rule-promotion.js +3 -3
  124. package/dist/semantic-rule-verifier.js +3 -3
  125. package/dist/source-agent-qualifier.js +3 -3
  126. package/dist/standup/build.js +3 -3
  127. package/dist/storage.js +2 -2
  128. package/dist/support-passport/index.js +15 -15
  129. package/dist/transfer/export-okf.js +3 -3
  130. package/dist/verified-recall.js +3 -3
  131. package/dist/who-knows-cli.js +4 -4
  132. package/dist/who-knows.js +3 -3
  133. package/package.json +2 -2
  134. package/src/access-entity-principal.test.ts +190 -0
  135. package/src/access-http.ts +2 -2
  136. package/src/access-operations-batch.ts +2 -2
  137. package/src/access-service.ts +7 -6
  138. package/src/chat/chat-executor.ts +1 -1
  139. package/src/extraction-error-classification.ts +23 -0
  140. package/src/extraction-local-failure-class.test.ts +55 -0
  141. package/src/extraction.ts +9 -9
  142. package/src/namespaces/search.ts +1 -1
  143. package/src/orchestration/extraction-run.test.ts +83 -2
  144. package/src/orchestration/qmd-result-resolver.ts +26 -1
  145. package/src/orchestration/recall-search-pipeline.ts +11 -12
  146. package/dist/chunk-7IANFJ24.js.map +0 -1
  147. package/dist/chunk-RWW5KPIY.js.map +0 -1
  148. package/dist/chunk-SJHFU52W.js.map +0 -1
  149. package/dist/chunk-VIS23B6O.js.map +0 -1
  150. /package/dist/{chunk-NJ2DZYEK.js.map → chunk-2T6KEOUL.js.map} +0 -0
  151. /package/dist/{chunk-BUKZ7MTN.js.map → chunk-5TRU6QQC.js.map} +0 -0
  152. /package/dist/{chunk-SRPGA6WN.js.map → chunk-7BCYAJKP.js.map} +0 -0
  153. /package/dist/{chunk-JRTKYFFY.js.map → chunk-AY6LO7GD.js.map} +0 -0
  154. /package/dist/{chunk-GPVGAHXL.js.map → chunk-CNAL35D5.js.map} +0 -0
  155. /package/dist/{chunk-U66CTIF5.js.map → chunk-F2RDRE7M.js.map} +0 -0
  156. /package/dist/{chunk-B7VKRCAG.js.map → chunk-FNEYSTB6.js.map} +0 -0
  157. /package/dist/{chunk-GSBSGT43.js.map → chunk-FPHU6KVI.js.map} +0 -0
  158. /package/dist/{chunk-RO2NNWR5.js.map → chunk-GAUS4XGN.js.map} +0 -0
  159. /package/dist/{chunk-4HJ2QK32.js.map → chunk-GDOI4U4D.js.map} +0 -0
  160. /package/dist/{chunk-IZRNMXZC.js.map → chunk-GRN4BVPM.js.map} +0 -0
  161. /package/dist/{chunk-VCOCOBUS.js.map → chunk-GZMHBRIR.js.map} +0 -0
  162. /package/dist/{chunk-TTQYX7VU.js.map → chunk-HN35OH2C.js.map} +0 -0
  163. /package/dist/{chunk-F2IU6A2L.js.map → chunk-HSNV4CSW.js.map} +0 -0
  164. /package/dist/{chunk-EKR6OO4P.js.map → chunk-I7CWAJHX.js.map} +0 -0
  165. /package/dist/{chunk-LJXBFVI2.js.map → chunk-ICTJOOWZ.js.map} +0 -0
  166. /package/dist/{chunk-IAOW2L6K.js.map → chunk-IJJWM2ZA.js.map} +0 -0
  167. /package/dist/{chunk-6Z2ZWBOV.js.map → chunk-IWDFHVPJ.js.map} +0 -0
  168. /package/dist/{chunk-GCW7UT5I.js.map → chunk-JJSMSS45.js.map} +0 -0
  169. /package/dist/{chunk-O2O4FDAH.js.map → chunk-JXQTECMU.js.map} +0 -0
  170. /package/dist/{chunk-ZXFKG7VZ.js.map → chunk-LB6HIXQC.js.map} +0 -0
  171. /package/dist/{chunk-B3I2QQU7.js.map → chunk-LWIU5BMA.js.map} +0 -0
  172. /package/dist/{chunk-WQYS47G4.js.map → chunk-N3PUGD7E.js.map} +0 -0
  173. /package/dist/{chunk-JORSMFL6.js.map → chunk-NBEM5QEH.js.map} +0 -0
  174. /package/dist/{chunk-JQCP5QW3.js.map → chunk-O67H7OXJ.js.map} +0 -0
  175. /package/dist/{chunk-4LLV2N3R.js.map → chunk-ONIVKGNC.js.map} +0 -0
  176. /package/dist/{chunk-WT2VVYBM.js.map → chunk-PJXSBXEC.js.map} +0 -0
  177. /package/dist/{chunk-ANV373S5.js.map → chunk-PW63SXFM.js.map} +0 -0
  178. /package/dist/{chunk-RIAKNXZB.js.map → chunk-Q77SLBWT.js.map} +0 -0
  179. /package/dist/{chunk-22YJQTPN.js.map → chunk-QGNZ5QPV.js.map} +0 -0
  180. /package/dist/{chunk-GE5ZNDLE.js.map → chunk-R42VUN5V.js.map} +0 -0
  181. /package/dist/{chunk-LPD6I2U6.js.map → chunk-SLVR3U4E.js.map} +0 -0
  182. /package/dist/{chunk-JZFJZLJY.js.map → chunk-UGV256H3.js.map} +0 -0
  183. /package/dist/{chunk-L74NWAD4.js.map → chunk-UKIIAEJF.js.map} +0 -0
  184. /package/dist/{chunk-5Q6CR65T.js.map → chunk-VTZSIG6O.js.map} +0 -0
  185. /package/dist/{chunk-4UT2NUAC.js.map → chunk-WJMAWDAU.js.map} +0 -0
  186. /package/dist/{chunk-JT3IXJIL.js.map → chunk-XBHUMPIQ.js.map} +0 -0
  187. /package/dist/{chunk-JM6U3SAE.js.map → chunk-XJ77IOAD.js.map} +0 -0
  188. /package/dist/{chunk-ODJ2HRFW.js.map → chunk-XXBCUIYQ.js.map} +0 -0
  189. /package/dist/{chunk-JOOGPZJE.js.map → chunk-XXW3P3RX.js.map} +0 -0
  190. /package/dist/{chunk-P3YUEPSQ.js.map → chunk-Y473XFTD.js.map} +0 -0
package/src/extraction.ts CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  import { applyGroundingWithConnector, headerConnector, renderExtractionConversation, resolveSourceConnector, type ExtractionGroundingContext } from "./source-agent-qualifier.js";
39
39
  import { isMemoryCategory } from "./write-envelope.js";
40
40
  export { classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog } from "./extraction-error-classification.js";
41
- import { classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog } from "./extraction-error-classification.js";
41
+ import { LocalLlmRejectedError, classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog, localRejectionStatus, type LocalFailureDiag } from "./extraction-error-classification.js";
42
42
  import { AMBIENT_CAPTURE_PROMPT_SECTION_COMPACT, clampAmbientCaptureConfidence } from "./ambient-provenance.js";
43
43
  import {
44
44
  CONSOLIDATION_RESPONSE_SCHEMA,
@@ -1244,7 +1244,7 @@ export class ExtractionEngine {
1244
1244
  entities: [],
1245
1245
  questions: [],
1246
1246
  extractionFailure: "local_llm_error",
1247
- extractionFailureClass: "provider_retryable",
1247
+ extractionFailureClass: err instanceof LocalLlmRejectedError ? "parse_empty" : classifyExtractionThrownError(err),
1248
1248
  };
1249
1249
  }
1250
1250
  log.info("extraction: local LLM error, falling back to gateway default AI:", err);
@@ -1494,9 +1494,8 @@ ${extractionResponseShape(this.config.extraction?.spanMode ?? "off")}
1494
1494
  Conversation:
1495
1495
  ${truncatedConversation}`;
1496
1496
 
1497
- log.debug(
1498
- `extractWithLocalLlm: calling localLlm.chatCompletion with prompt length ${localPrompt.length}...`,
1499
- );
1497
+ log.debug(`extractWithLocalLlm: calling localLlm.chatCompletion with prompt length ${localPrompt.length}...`);
1498
+ const failureDiag: LocalFailureDiag = {};
1500
1499
  const response = await this.localLlm.chatCompletion(
1501
1500
  [
1502
1501
  { role: "system", content: "You are a memory extraction system. Output valid JSON only." },
@@ -1512,9 +1511,12 @@ ${truncatedConversation}`;
1512
1511
  : undefined,
1513
1512
  priority: "background",
1514
1513
  signal,
1514
+ failureDiag,
1515
1515
  },
1516
1516
  );
1517
1517
  if (!response?.content) {
1518
+ const rejected = localRejectionStatus(failureDiag);
1519
+ if (rejected !== undefined) throw new LocalLlmRejectedError(rejected); // the endpoint is up and refuses this input
1518
1520
  log.debug("extractWithLocalLlm: chatCompletion returned null or empty content");
1519
1521
  return null;
1520
1522
  }
@@ -1547,12 +1549,10 @@ ${truncatedConversation}`;
1547
1549
  log.debug("extractWithLocalLlm: JSON parse failed, attempting partial extraction...");
1548
1550
  const partial = this.extractPartialFacts(content);
1549
1551
  if (partial.facts.length > 0 || partial.entities.length > 0) {
1550
- log.debug(
1551
- `extractWithLocalLlm: extracted ${partial.facts.length} partial facts from truncated JSON`,
1552
- );
1552
+ log.debug(`extractWithLocalLlm: extracted ${partial.facts.length} partial facts from truncated JSON`);
1553
1553
  return partial;
1554
1554
  }
1555
- return null;
1555
+ throw new LocalLlmRejectedError(); // the endpoint answered and nothing in the answer is usable
1556
1556
  }
1557
1557
 
1558
1558
  /**
@@ -700,7 +700,7 @@ export function normalizeQmdResultPath(resultPath: string, collection: string):
700
700
  }
701
701
  return value;
702
702
  }
703
- function resolveNamespaceResultPath(
703
+ export function resolveNamespaceResultPath(
704
704
  memoryDir: string,
705
705
  collection: string,
706
706
  resultPath: string,
@@ -88,6 +88,7 @@ const migrationSummary: TierMigrationCycleSummary = {
88
88
 
89
89
  interface Harness {
90
90
  config: PluginConfig;
91
+ buffer: SmartBuffer;
91
92
  storageForNs: (ns: string) => Promise<StorageManager>;
92
93
  setStorageForDelay: (delayMs: number) => void;
93
94
  newCoordinator: () => ExtractionRunCoordinator;
@@ -105,7 +106,7 @@ interface Harness {
105
106
  coord: ExtractionRunCoordinator,
106
107
  content: string,
107
108
  opts?: { force?: boolean; failClosed?: boolean; writeNamespaceOverride?: string },
108
- ) => Promise<{ status: string; reason?: string }>;
109
+ ) => Promise<{ status: string; reason?: string; failureClass?: string; retainedBuffer?: boolean }>;
109
110
  cleanup: () => Promise<void>;
110
111
  passiveCapture: () => { principal?: string; namespace?: string } | null;
111
112
  setThreadingBlock: (blocked: boolean) => void;
@@ -201,6 +202,7 @@ async function makeHarness(overrides: Record<string, unknown> = {}): Promise<Har
201
202
 
202
203
  return {
203
204
  config,
205
+ buffer,
204
206
  storageForNs,
205
207
  setStorageForDelay: (delayMs) => {
206
208
  storageForDelayMs = delayMs;
@@ -243,7 +245,7 @@ async function makeHarness(overrides: Record<string, unknown> = {}): Promise<Har
243
245
  forceExtractionAttempt: opts.force === true,
244
246
  writeNamespaceOverride: opts.writeNamespaceOverride,
245
247
  });
246
- return { status: result.status, reason: result.reason };
248
+ return { status: result.status, reason: result.reason, failureClass: result.failureClass, retainedBuffer: result.retainedBuffer };
247
249
  },
248
250
  cleanup: async () => {
249
251
  StorageManager.clearAllStaticCaches();
@@ -1336,3 +1338,82 @@ test("#3140: a successful flush heals the recorded LLM health", async () => {
1336
1338
  await h.cleanup();
1337
1339
  }
1338
1340
  });
1341
+
1342
+ // ---------------------------------------------------------------------------
1343
+ // Wiring the trigger drain depends on (#3266): what a real run hands back, and what it asks of the buffer
1344
+ // ---------------------------------------------------------------------------
1345
+
1346
+ test("a retained failure carries the extraction's failure class, so the drain can tell an outage from a bad chunk", async () => {
1347
+ for (const cls of ["provider_retryable", "auth_config", "parse_empty"] as const) {
1348
+ const h = await makeHarness({ extractionRetryScheduleMs: [3_600_000], extractionBreakerFailureThreshold: 100 });
1349
+ try {
1350
+ h.setRespond(() => failureResult(cls));
1351
+ const result = await h.run(h.newCoordinator(), `class-${cls}`);
1352
+ assert.equal(result.retainedBuffer, true, `${cls}: the window is kept for a retry`);
1353
+ assert.equal(result.failureClass, cls, `${cls}: the class reaches the run result`);
1354
+ } finally {
1355
+ await h.cleanup();
1356
+ }
1357
+ }
1358
+ });
1359
+
1360
+ test("a failure with no class defaults to provider_retryable on the run result, like the retry bookkeeping", async () => {
1361
+ const h = await makeHarness({ extractionRetryScheduleMs: [3_600_000], extractionBreakerFailureThreshold: 100 });
1362
+ try {
1363
+ h.setRespond(() => ({ ...failureResult("parse_empty"), extractionFailureClass: undefined }));
1364
+ const result = await h.run(h.newCoordinator(), "class-default");
1365
+ assert.equal(result.failureClass, "provider_retryable");
1366
+ } finally {
1367
+ await h.cleanup();
1368
+ }
1369
+ });
1370
+
1371
+ test("a run without a failure reports no failure class", async () => {
1372
+ const h = await makeHarness();
1373
+ try {
1374
+ h.setRespond(() => ({ ...successResult(), facts: [] }));
1375
+ const result = await h.run(h.newCoordinator(), "class-none");
1376
+ assert.equal(result.failureClass, undefined);
1377
+ } finally {
1378
+ await h.cleanup();
1379
+ }
1380
+ });
1381
+
1382
+ test("the retention window a chunk run is given reaches the buffer as the processed snapshot (#3266)", async () => {
1383
+ const h = await makeHarness();
1384
+ try {
1385
+ const seen: Array<readonly BufferTurn[] | undefined> = [];
1386
+ const original = h.buffer.retainDeferredTurns.bind(h.buffer);
1387
+ h.buffer.retainDeferredTurns = async (key, turns, max, processed) => {
1388
+ seen.push(processed);
1389
+ await original(key, turns, max, processed);
1390
+ };
1391
+ const chunk = makeTurns("chunk-turns");
1392
+ const window = [...makeTurns("earlier-turns"), ...chunk];
1393
+ h.setRespond(() => successResult());
1394
+ await h.newCoordinator().runExtraction(chunk, {
1395
+ skipCharThreshold: true,
1396
+ skipUserTurnThreshold: true,
1397
+ clearBufferAfterExtraction: true,
1398
+ bufferKey: "window",
1399
+ failOnExtractionFailure: false,
1400
+ forceExtractionAttempt: false,
1401
+ retentionWindow: window,
1402
+ });
1403
+ assert.equal(seen.length, 1, "the run asked the buffer to update its retention slot once");
1404
+ assert.deepEqual(seen[0], window, "with the window it was given, not just its own chunk");
1405
+
1406
+ seen.length = 0;
1407
+ await h.newCoordinator().runExtraction(chunk, {
1408
+ skipCharThreshold: true,
1409
+ skipUserTurnThreshold: true,
1410
+ clearBufferAfterExtraction: true,
1411
+ bufferKey: "window",
1412
+ failOnExtractionFailure: false,
1413
+ forceExtractionAttempt: false,
1414
+ });
1415
+ assert.deepEqual(seen[0], chunk, "without a window the run keeps naming its own turns");
1416
+ } finally {
1417
+ await h.cleanup();
1418
+ }
1419
+ });
@@ -26,12 +26,13 @@
26
26
  * RecallRerankCoordinator / ConversationIndexCoordinator accessor pattern.
27
27
  */
28
28
 
29
+ import { existsSync } from "node:fs";
29
30
  import path from "node:path";
30
31
  import { resolveNamespaceCapabilities } from "../capabilities.js";
31
32
  import type { StorageManager } from "../index.js";
32
33
  import { log } from "../logger.js";
33
34
  import { namespaceIdentityFromToken } from "../namespaces/identity.js";
34
- import { normalizeQmdResultPath } from "../namespaces/search.js";
35
+ import { normalizeQmdResultPath, resolveNamespaceResultPath } from "../namespaces/search.js";
35
36
  import { SecureStoreLockedError } from "../secure-store/index.js";
36
37
  import { isPathInsideStorageRoot } from "../storage-paths.js";
37
38
  import { isSupportPassportPrivateMemory } from "../support-passport/card-projection.js";
@@ -136,6 +137,30 @@ export function qmdResultPathCandidates(storageDir: string, resultPath: string):
136
137
  return [...candidates];
137
138
  }
138
139
 
140
+ /** Canonicalize flat hot-memory identities before merging with native hits (#3194).
141
+ * This is a path-only map: no candidates, collections or namespace scopes are removed.
142
+ * Cold/explicit foreign collections keep their existing owner-resolution path.
143
+ */
144
+ export function normalizeFlatQmdResults(
145
+ results: QmdSearchResult[],
146
+ config: PluginConfig,
147
+ collection = config.qmdCollection,
148
+ ): QmdSearchResult[] {
149
+ if (!collection || resolveNamespaceCapabilities(config).namespaces || collection !== config.qmdCollection) return results;
150
+ return results.map((result) => ({
151
+ ...result,
152
+ path: withHotFactsPath(config.memoryDir, resolveNamespaceResultPath(config.memoryDir, collection, result.path)),
153
+ }));
154
+ }
155
+
156
+ /** A hot-facts collection registered at the facts/ subtree returns date-relative paths (#2111); meet the loaded memory's path. */
157
+ function withHotFactsPath(memoryDir: string, resolved: string): string {
158
+ const relative = path.relative(path.resolve(memoryDir), resolved).split(path.sep).join("/");
159
+ if (!/^\d{4}-\d{2}-\d{2}\//.test(relative) || existsSync(resolved)) return resolved;
160
+ const hot = path.join(path.resolve(memoryDir), "facts", relative);
161
+ return existsSync(hot) ? hot : resolved;
162
+ }
163
+
139
164
  /**
140
165
  * Extract a non-empty `dir` from a storage, returning `null` when the
141
166
  * property is absent or not a non-empty string at runtime. Proxy-backed
@@ -27,7 +27,7 @@ import { inferIntentFromText, intentCompatibilityScore } from "../intent.js";
27
27
  import { log } from "../logger.js";
28
28
  import { NamespaceStorageRouter } from "../namespaces/storage.js";
29
29
  import { NegativeExampleStore } from "../negative.js";
30
- import { qmdCollectionPathParts } from "./qmd-result-resolver.js";
30
+ import { normalizeFlatQmdResults, qmdCollectionPathParts } from "./qmd-result-resolver.js";
31
31
  import type { RecallRerankCoordinator, RecallResultPartitionSink } from "./recall-rerank-coordinator.js";
32
32
  import type { GraphRecallExpansionOptions, GraphRecallExpansionResult } from "./graph-recall-coordinator.js";
33
33
  import { RelevanceStore } from "../relevance.js";
@@ -394,7 +394,7 @@ export class RecallSearchPipelineCoordinator {
394
394
  break;
395
395
  }
396
396
 
397
- const primaryResults = options.collection
397
+ let primaryResults = options.collection
398
398
  ? options.abortSignal
399
399
  ? await this.deps.qmd.search(
400
400
  prompt,
@@ -426,6 +426,7 @@ export class RecallSearchPipelineCoordinator {
426
426
  onDegradation: options.onDegradation,
427
427
  },
428
428
  });
429
+ primaryResults = normalizeFlatQmdResults(primaryResults, this.deps.config, options.collection);
429
430
  lastPrimaryResultCount = primaryResults.length;
430
431
  lastHybridResultCount = 0;
431
432
  lastHybridTopUpUsed = false;
@@ -452,7 +453,7 @@ export class RecallSearchPipelineCoordinator {
452
453
  // uniform across primary + top-up (gotcha #39). Issue #1335 (codex review #1422).
453
454
  lastHybridTopUpSkippedReason = "lex_strategy";
454
455
  } else {
455
- const hybridResults = options.collection
456
+ let hybridResults = options.collection
456
457
  ? await this.deps.qmd.hybridSearch(
457
458
  prompt,
458
459
  options.collection,
@@ -474,6 +475,7 @@ export class RecallSearchPipelineCoordinator {
474
475
  onDegradation: options.onDegradation,
475
476
  },
476
477
  });
478
+ hybridResults = normalizeFlatQmdResults(hybridResults, this.deps.config, options.collection);
477
479
  lastHybridResultCount = hybridResults.length;
478
480
  lastHybridTopUpUsed = hybridResults.length > 0;
479
481
  if (hybridResults.length > 0) {
@@ -740,9 +742,7 @@ export class RecallSearchPipelineCoordinator {
740
742
  // limps home later cannot mutate a recorded recall's collector.
741
743
  coldQmdObserverActive = false;
742
744
  if (longTerm.length > 0) {
743
- log.debug(
744
- `cold-tier recall source=cold-qmd collection=${coldCollection} hits=${longTerm.length}`,
745
- );
745
+ log.debug(`cold-tier recall source=cold-qmd collection=${coldCollection} hits=${longTerm.length}`);
746
746
  }
747
747
  }
748
748
  }
@@ -940,9 +940,7 @@ export class RecallSearchPipelineCoordinator {
940
940
  }
941
941
  }
942
942
  if (caps.rerank && this.deps.config.rerankProvider === "cloud") {
943
- log.debug(
944
- "rerankProvider=cloud is reserved/experimental in v2.2.0; skipping rerank",
945
- );
943
+ log.debug("rerankProvider=cloud is reserved/experimental in v2.2.0; skipping rerank");
946
944
  }
947
945
 
948
946
  if (caps.recallTrustScore && results.length > 0) {
@@ -1149,6 +1147,9 @@ export class RecallSearchPipelineCoordinator {
1149
1147
  : null;
1150
1148
  const buildRecallRow = (r: QmdSearchResult, memory: MemoryFile | undefined): QmdSearchResult => ({
1151
1149
  ...r,
1150
+ // Render loaded flat memories, not QMD's front-matter/chunk preview (#3194).
1151
+ snippet: memory && !resolveNamespaceCapabilities(this.deps.config).namespaces ? memory.content : r.snippet,
1152
+ line: memory && !resolveNamespaceCapabilities(this.deps.config).namespaces ? undefined : r.line,
1152
1153
  sourceConnector: memory ? memory.frontmatter.sourceConnector : undefined,
1153
1154
  origin: memory ? memory.frontmatter.origin : undefined,
1154
1155
  // #1952: an active state view carries the chain fields the inject
@@ -1358,9 +1359,7 @@ export class RecallSearchPipelineCoordinator {
1358
1359
  }
1359
1360
  }
1360
1361
  if (connectorPartitionFilteredCount > 0) {
1361
- log.debug(
1362
- `connector partition filter removed ${connectorPartitionFilteredCount} tool-scoped candidates from recall`,
1363
- );
1362
+ log.debug(`connector partition filter removed ${connectorPartitionFilteredCount} tool-scoped candidates from recall`);
1364
1363
  }
1365
1364
  if (supportPassportFilteredCount > 0) {
1366
1365
  log.debug(