@remnic/core 9.40.0 → 9.42.0

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 (278) hide show
  1. package/dist/access-admin-ops-surface.d.ts +10 -10
  2. package/dist/access-admin-ops-surface.js +5 -4
  3. package/dist/access-authorization-probe.d.ts +10 -10
  4. package/dist/access-authorization-probe.js +6 -5
  5. package/dist/access-boundary.d.ts +10 -10
  6. package/dist/access-boundary.js +5 -4
  7. package/dist/access-cli.js +16 -15
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +10 -10
  10. package/dist/access-extraction-force-flush.js +5 -4
  11. package/dist/access-health-types.d.ts +1 -1
  12. package/dist/access-http-lcm-compaction.d.ts +10 -10
  13. package/dist/access-http-lifecycle-flush.d.ts +10 -10
  14. package/dist/access-http.d.ts +10 -10
  15. package/dist/access-http.js +10 -9
  16. package/dist/access-identity-continuity-surface.d.ts +8 -8
  17. package/dist/access-identity-continuity-surface.js +5 -4
  18. package/dist/access-lcm-surface.d.ts +10 -10
  19. package/dist/access-lcm-surface.js +5 -4
  20. package/dist/access-mcp.d.ts +10 -10
  21. package/dist/access-mcp.js +8 -7
  22. package/dist/access-memory-search-fanout.d.ts +2 -2
  23. package/dist/{access-namespace-preflight-BbDJBq0e.d.ts → access-namespace-preflight-47dOyty9.d.ts} +1 -1
  24. package/dist/access-namespace-preflight.d.ts +3 -3
  25. package/dist/access-namespace-preflight.js +5 -4
  26. package/dist/access-observe-write-surface.d.ts +10 -10
  27. package/dist/access-observe-write-surface.js +5 -4
  28. package/dist/access-operations-batch.js +6 -5
  29. package/dist/access-operations.d.ts +13 -13
  30. package/dist/access-operations.js +7 -6
  31. package/dist/access-recall-concurrency.d.ts +10 -10
  32. package/dist/access-recall-concurrency.js +5 -4
  33. package/dist/access-recall-response.d.ts +10 -10
  34. package/dist/access-recall-response.js +5 -4
  35. package/dist/access-recall-surface.d.ts +10 -10
  36. package/dist/access-recall-surface.js +5 -4
  37. package/dist/access-schema.d.ts +84 -84
  38. package/dist/{access-service-B2h6rITp.d.ts → access-service-D1BkxwSf.d.ts} +7 -7
  39. package/dist/access-service-helpers.d.ts +10 -10
  40. package/dist/access-service.d.ts +10 -10
  41. package/dist/access-service.js +5 -4
  42. package/dist/access-surface-catalog.d.ts +10 -10
  43. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  44. package/dist/action-confidence.d.ts +1 -1
  45. package/dist/active-memory-bridge.d.ts +1 -1
  46. package/dist/active-recall.d.ts +1 -1
  47. package/dist/active-recall.js +2 -2
  48. package/dist/behavior-learner.d.ts +1 -1
  49. package/dist/behavior-signals.d.ts +1 -1
  50. package/dist/bootstrap.d.ts +8 -8
  51. package/dist/briefing.d.ts +2 -2
  52. package/dist/buffer-surprise-report.d.ts +1 -1
  53. package/dist/buffer-turn-helpers.d.ts +1 -1
  54. package/dist/buffer.d.ts +2 -2
  55. package/dist/bulk-import/index.d.ts +3 -3
  56. package/dist/calibration.d.ts +1 -1
  57. package/dist/capabilities.d.ts +1 -1
  58. package/dist/{catalog-Bz2LxGNb.d.ts → catalog-DPQynQZG.d.ts} +1 -1
  59. package/dist/causal-behavior.d.ts +1 -1
  60. package/dist/causal-consolidation.d.ts +1 -1
  61. package/dist/causal-trajectory-graph.d.ts +1 -1
  62. package/dist/{chunk-443XCR34.js → chunk-3SG4FGDV.js} +53 -41
  63. package/dist/chunk-3SG4FGDV.js.map +1 -0
  64. package/dist/{chunk-XPQ5ZTL6.js → chunk-6I2C3EOH.js} +6 -6
  65. package/dist/chunk-7VIB7PAE.js +142 -0
  66. package/dist/chunk-7VIB7PAE.js.map +1 -0
  67. package/dist/{chunk-AYICY7IE.js → chunk-AXUU7SG3.js} +2 -2
  68. package/dist/{chunk-RK6WMGTX.js → chunk-CQ5MR2HQ.js} +13 -9
  69. package/dist/chunk-CQ5MR2HQ.js.map +1 -0
  70. package/dist/{chunk-KW4ZFTUS.js → chunk-CWPDM66T.js} +5 -5
  71. package/dist/{chunk-NEO4QCID.js → chunk-FHEJ22B7.js} +10 -4
  72. package/dist/chunk-FHEJ22B7.js.map +1 -0
  73. package/dist/{chunk-EBWOOJEP.js → chunk-GDC4INBF.js} +2 -2
  74. package/dist/{chunk-UIVRPXRR.js → chunk-KXGN6G2Y.js} +3 -3
  75. package/dist/chunk-KXGN6G2Y.js.map +1 -0
  76. package/dist/{chunk-AXHMCRS3.js → chunk-OYFDEJZB.js} +23 -6
  77. package/dist/chunk-OYFDEJZB.js.map +1 -0
  78. package/dist/{chunk-SD4A6IZY.js → chunk-PWKZV4A3.js} +2 -2
  79. package/dist/{chunk-XYXYEDPL.js → chunk-Q4XL7LJW.js} +26 -5
  80. package/dist/chunk-Q4XL7LJW.js.map +1 -0
  81. package/dist/{chunk-MOHZIKZM.js → chunk-TM72UOX2.js} +2 -2
  82. package/dist/{chunk-GLBK35BT.js → chunk-UGCUVLOL.js} +14 -10
  83. package/dist/chunk-UGCUVLOL.js.map +1 -0
  84. package/dist/{chunk-Q6QYN56R.js → chunk-UQVGDEAJ.js} +2 -2
  85. package/dist/{chunk-KYYRJVE6.js → chunk-XRWVXPEM.js} +2 -2
  86. package/dist/{cli-CO8qKPM5.d.ts → cli-IUhV-lmk.d.ts} +5 -5
  87. package/dist/cli.d.ts +12 -12
  88. package/dist/cli.js +15 -14
  89. package/dist/compounding/engine.d.ts +2 -2
  90. package/dist/compounding/preference-consolidator.d.ts +1 -1
  91. package/dist/compression-optimizer.d.ts +1 -1
  92. package/dist/config.d.ts +1 -1
  93. package/dist/config.js +2 -2
  94. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  95. package/dist/connectors/codex-materialize.d.ts +1 -1
  96. package/dist/connectors/index.d.ts +1 -1
  97. package/dist/consolidation-provenance-check.d.ts +2 -2
  98. package/dist/consolidation-undo.d.ts +2 -2
  99. package/dist/contradiction/index.d.ts +2 -2
  100. package/dist/conversation-index/backend.d.ts +1 -1
  101. package/dist/conversation-index/chunker.d.ts +1 -1
  102. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  103. package/dist/conversation-index/indexer.d.ts +1 -1
  104. package/dist/conversation-index/search.d.ts +1 -1
  105. package/dist/corpus-watermark.d.ts +7 -1
  106. package/dist/corpus-watermark.js +2 -2
  107. package/dist/day-summary.d.ts +1 -1
  108. package/dist/delinearize.d.ts +1 -1
  109. package/dist/direct-answer-wiring.d.ts +1 -1
  110. package/dist/direct-answer.d.ts +1 -1
  111. package/dist/embedding-fallback.d.ts +1 -1
  112. package/dist/enrichment/index.d.ts +1 -1
  113. package/dist/entity-retrieval.d.ts +2 -2
  114. package/dist/entity-schema.d.ts +1 -1
  115. package/dist/explicit-capture.d.ts +8 -8
  116. package/dist/extraction-error-classification.d.ts +1 -1
  117. package/dist/extraction-faithfulness.d.ts +1 -1
  118. package/dist/extraction-judge-telemetry.d.ts +1 -1
  119. package/dist/extraction-judge-training.d.ts +1 -1
  120. package/dist/extraction-judge.d.ts +1 -1
  121. package/dist/extraction-liveness.d.ts +1 -1
  122. package/dist/extraction-liveness.js +3 -1
  123. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  124. package/dist/extraction-source-grounding.d.ts +1 -1
  125. package/dist/extraction.d.ts +1 -1
  126. package/dist/fallback-llm.d.ts +1 -1
  127. package/dist/graph-dashboard-diff.d.ts +1 -1
  128. package/dist/graph-dashboard-key.d.ts +1 -1
  129. package/dist/graph-dashboard-parser.d.ts +1 -1
  130. package/dist/graph-edge-reinforcement.d.ts +1 -1
  131. package/dist/graph-snapshot.d.ts +1 -1
  132. package/dist/graph.d.ts +1 -1
  133. package/dist/importance.d.ts +1 -1
  134. package/dist/importers/index.d.ts +1 -1
  135. package/dist/in-flight-reads.d.ts +1 -1
  136. package/dist/index.d.ts +407 -407
  137. package/dist/index.js +16 -15
  138. package/dist/intent.d.ts +1 -1
  139. package/dist/lcm/engine.d.ts +1 -1
  140. package/dist/lcm/index.d.ts +1 -1
  141. package/dist/lcm/tools.d.ts +1 -1
  142. package/dist/lifecycle.d.ts +1 -1
  143. package/dist/live-connectors-runner.d.ts +1 -1
  144. package/dist/local-llm.d.ts +1 -1
  145. package/dist/local-model-endpoint.d.ts +1 -1
  146. package/dist/maintenance/memory-governance.d.ts +1 -1
  147. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  148. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  149. package/dist/{maintenance-D2jkiT9r.d.ts → maintenance-DVBLXkFw.d.ts} +3 -3
  150. package/dist/mcp-memory-inspector-app.d.ts +10 -10
  151. package/dist/memory-action-policy.d.ts +1 -1
  152. package/dist/memory-cache.d.ts +1 -1
  153. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  154. package/dist/memory-projection-store.d.ts +1 -1
  155. package/dist/memory-provenance.d.ts +1 -1
  156. package/dist/memory-worth-outcomes.d.ts +2 -2
  157. package/dist/models-json.d.ts +1 -1
  158. package/dist/namespaces/migrate.d.ts +4 -3
  159. package/dist/namespaces/migrate.js +1 -1
  160. package/dist/namespaces/principal.d.ts +1 -1
  161. package/dist/namespaces/search.d.ts +1 -1
  162. package/dist/namespaces/storage.d.ts +3 -3
  163. package/dist/native-knowledge.d.ts +1 -1
  164. package/dist/offline-sync-impression-drain.d.ts +1 -1
  165. package/dist/operator-doctor-corpus.d.ts +1 -1
  166. package/dist/operator-doctor-corpus.js +3 -3
  167. package/dist/operator-doctor-replica.d.ts +1 -1
  168. package/dist/operator-toolkit.d.ts +5 -4
  169. package/dist/operator-toolkit.js +7 -6
  170. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  171. package/dist/orchestration/maintenance.d.ts +4 -4
  172. package/dist/{orchestrator-SHaRXCUF.d.ts → orchestrator-fYTncnLy.d.ts} +7 -7
  173. package/dist/orchestrator.d.ts +8 -8
  174. package/dist/orchestrator.js +16 -15
  175. package/dist/patterns-cli.d.ts +1 -1
  176. package/dist/{pipeline-B5_tS1Yc.d.ts → pipeline-Cs76XC-p.d.ts} +1 -1
  177. package/dist/policy-runtime.d.ts +1 -1
  178. package/dist/proactive-contention.d.ts +1 -1
  179. package/dist/provenance.d.ts +1 -1
  180. package/dist/{qmd-CkEV_SPG.d.ts → qmd-WJwvBgiF.d.ts} +1 -1
  181. package/dist/qmd-preflight.d.ts +2 -2
  182. package/dist/qmd-recall-cache.d.ts +1 -1
  183. package/dist/qmd.d.ts +2 -2
  184. package/dist/recall-concurrency-config.d.ts +1 -1
  185. package/dist/recall-disclosure-escalation.d.ts +1 -1
  186. package/dist/recall-explain-renderer.d.ts +1 -1
  187. package/dist/recall-memory-map.d.ts +1 -1
  188. package/dist/recall-planner-llm.d.ts +1 -1
  189. package/dist/recall-state.d.ts +1 -1
  190. package/dist/recall-tag-filter.d.ts +1 -1
  191. package/dist/recall-timings.d.ts +1 -1
  192. package/dist/recall-xray-cli.d.ts +1 -1
  193. package/dist/recall-xray-renderer.d.ts +1 -1
  194. package/dist/recall-xray.d.ts +1 -1
  195. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  196. package/dist/replay/normalizers/claude.d.ts +1 -1
  197. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  198. package/dist/replay/normalizers/shared.d.ts +1 -1
  199. package/dist/replay/runner.d.ts +1 -1
  200. package/dist/replay/types.d.ts +1 -1
  201. package/dist/replica-divergence.d.ts +1 -1
  202. package/dist/replica-peers-config.d.ts +1 -1
  203. package/dist/resolve-auth-token.d.ts +1 -1
  204. package/dist/resume-bundles.js +3 -3
  205. package/dist/retrieval-agents.d.ts +2 -2
  206. package/dist/retrieval-tiers.d.ts +1 -1
  207. package/dist/routing/engine.d.ts +1 -1
  208. package/dist/routing/store.d.ts +1 -1
  209. package/dist/salvage-envelope.d.ts +1 -1
  210. package/dist/schemas.d.ts +76 -76
  211. package/dist/{scope-profiles-DeAdnocS.d.ts → scope-profiles-CQ0NAleS.d.ts} +1 -1
  212. package/dist/search/embed-helper.d.ts +1 -1
  213. package/dist/search/factory.d.ts +1 -1
  214. package/dist/search/index.d.ts +1 -1
  215. package/dist/search/lancedb-backend.d.ts +1 -1
  216. package/dist/search/meilisearch-backend.d.ts +1 -1
  217. package/dist/search/noop-backend.d.ts +1 -1
  218. package/dist/search/orama-backend.d.ts +1 -1
  219. package/dist/search/port.d.ts +1 -1
  220. package/dist/search/remote-backend.d.ts +1 -1
  221. package/dist/{semantic-consolidation-DR_h-JUD.d.ts → semantic-consolidation-BWxOBMtz.d.ts} +1 -1
  222. package/dist/semantic-consolidation.d.ts +2 -2
  223. package/dist/semantic-rule-verifier.d.ts +1 -1
  224. package/dist/{service-BTb94UPJ.d.ts → service-CLuMO-pa.d.ts} +2 -2
  225. package/dist/session-observer-bands.d.ts +1 -1
  226. package/dist/session-observer-state.d.ts +1 -1
  227. package/dist/shared-context/manager.d.ts +9 -9
  228. package/dist/signal.d.ts +1 -1
  229. package/dist/source-agent-qualifier.d.ts +1 -1
  230. package/dist/{storage-ax2EIsVS.d.ts → storage-Blr71GpS.d.ts} +1 -1
  231. package/dist/storage.d.ts +2 -2
  232. package/dist/summarizer.d.ts +1 -1
  233. package/dist/summary-snapshot.d.ts +1 -1
  234. package/dist/temporal-supersession.d.ts +2 -2
  235. package/dist/temporal-timeline-recall.d.ts +1 -1
  236. package/dist/temporal-validity.d.ts +1 -1
  237. package/dist/threading.d.ts +1 -1
  238. package/dist/tier-migration.d.ts +2 -2
  239. package/dist/tier-routing.d.ts +1 -1
  240. package/dist/topics.d.ts +1 -1
  241. package/dist/transcript.d.ts +1 -1
  242. package/dist/transfer/types.d.ts +66 -66
  243. package/dist/trust-score-stage.d.ts +1 -1
  244. package/dist/trust-score.d.ts +1 -1
  245. package/dist/{types-CCGL-9NX.d.ts → types-CqWwMPr_.d.ts} +41 -59
  246. package/dist/types.d.ts +1 -1
  247. package/dist/utility-runtime.d.ts +1 -1
  248. package/dist/write-envelope.d.ts +1 -1
  249. package/package.json +2 -2
  250. package/src/access-http.test.ts +2 -2
  251. package/src/access-service-health.test.ts +39 -17
  252. package/src/access-service.ts +25 -3
  253. package/src/cli.ts +3 -3
  254. package/src/corpus-watermark.test.ts +16 -0
  255. package/src/corpus-watermark.ts +28 -5
  256. package/src/extraction-liveness.test.ts +81 -19
  257. package/src/extraction-liveness.ts +96 -98
  258. package/src/extraction-watermark.test.ts +290 -0
  259. package/src/namespaces/migrate.ts +10 -2
  260. package/src/operator-doctor-corpus.test.ts +7 -0
  261. package/src/operator-doctor-corpus.ts +1 -1
  262. package/src/operator-toolkit.ts +8 -8
  263. package/src/orchestration/extraction-watermark.ts +188 -0
  264. package/dist/chunk-443XCR34.js.map +0 -1
  265. package/dist/chunk-AXHMCRS3.js.map +0 -1
  266. package/dist/chunk-GLBK35BT.js.map +0 -1
  267. package/dist/chunk-NEO4QCID.js.map +0 -1
  268. package/dist/chunk-RK6WMGTX.js.map +0 -1
  269. package/dist/chunk-UIVRPXRR.js.map +0 -1
  270. package/dist/chunk-XYXYEDPL.js.map +0 -1
  271. /package/dist/{chunk-XPQ5ZTL6.js.map → chunk-6I2C3EOH.js.map} +0 -0
  272. /package/dist/{chunk-AYICY7IE.js.map → chunk-AXUU7SG3.js.map} +0 -0
  273. /package/dist/{chunk-KW4ZFTUS.js.map → chunk-CWPDM66T.js.map} +0 -0
  274. /package/dist/{chunk-EBWOOJEP.js.map → chunk-GDC4INBF.js.map} +0 -0
  275. /package/dist/{chunk-SD4A6IZY.js.map → chunk-PWKZV4A3.js.map} +0 -0
  276. /package/dist/{chunk-MOHZIKZM.js.map → chunk-TM72UOX2.js.map} +0 -0
  277. /package/dist/{chunk-Q6QYN56R.js.map → chunk-UQVGDEAJ.js.map} +0 -0
  278. /package/dist/{chunk-KYYRJVE6.js.map → chunk-XRWVXPEM.js.map} +0 -0
@@ -194,6 +194,30 @@ test("evaluate: an unreadable buffer degrades with a distinct reason, even with
194
194
  assert.equal(disabled.degraded, false, "disabled gate suppresses even a read-failure degradation");
195
195
  });
196
196
 
197
+ test("evaluate: a warming aggregate watermark remains pending without degrading", () => {
198
+ const status = evaluateExtractionLiveness({
199
+ config: ENABLED,
200
+ lastExtractionAt: null,
201
+ snapshot: snapshot({ bufferedSessionCount: 1, pendingTurnCount: 2 }),
202
+ nowMs: NOW,
203
+ watermarkPending: true,
204
+ });
205
+ assert.equal(status.degraded, false);
206
+ assert.equal(status.watermarkPending, true);
207
+ });
208
+
209
+ test("evaluate: warming watermark does not suppress an independent buffer read failure", () => {
210
+ const status = evaluateExtractionLiveness({
211
+ config: ENABLED,
212
+ lastExtractionAt: null,
213
+ snapshot: snapshot({ readFailed: true, readError: "buffer unavailable" }),
214
+ nowMs: NOW,
215
+ watermarkPending: true,
216
+ });
217
+ assert.equal(status.degraded, true);
218
+ assert.match(status.degradedReason ?? "", /buffer unavailable/);
219
+ });
220
+
197
221
  test("evaluate: an unreadable watermark degrades with a distinct reason, even with an empty buffer (§22)", () => {
198
222
  const status = evaluateExtractionLiveness({
199
223
  config: ENABLED,
@@ -311,22 +335,14 @@ test("getBufferSnapshot: counts retained (deferred) turns as pending, not just a
311
335
  assert.equal(snap.oldestTurnTimestamp, "2026-04-01T00:00:00.000Z", "oldest is the first retained turn");
312
336
  });
313
337
 
314
- test("evaluate: emits watermarkScope, defaulting to root-store and honoring an explicit scope", () => {
315
- const rootScoped = evaluateExtractionLiveness({
316
- config: ENABLED,
317
- lastExtractionAt: null,
318
- snapshot: snapshot(),
319
- nowMs: NOW,
320
- });
321
- assert.equal(rootScoped.watermarkScope, "root-store", "defaults to root-store today (#2159)");
322
- const aggregate = evaluateExtractionLiveness({
338
+ test("evaluate: emits only the aggregate watermark scope", () => {
339
+ const status = evaluateExtractionLiveness({
323
340
  config: ENABLED,
324
341
  lastExtractionAt: null,
325
342
  snapshot: snapshot(),
326
343
  nowMs: NOW,
327
- watermarkScope: "aggregate",
328
344
  });
329
- assert.equal(aggregate.watermarkScope, "aggregate", "threads an explicit scope for #2159");
345
+ assert.equal(status.watermarkScope, "aggregate");
330
346
  });
331
347
 
332
348
  // ── ExtractionLivenessWarnThrottle ───────────────────────────────────────────
@@ -339,7 +355,7 @@ test("throttle: warns once per staleness window and again after it elapses; rese
339
355
  oldestBufferedTurnAgeMs: null,
340
356
  degraded: true,
341
357
  degradedReason: "stalled",
342
- watermarkScope: "root-store",
358
+ watermarkScope: "aggregate",
343
359
  };
344
360
  const healthy: ExtractionLivenessStatus = { ...degraded, degraded: false, degradedReason: null };
345
361
  const t = new ExtractionLivenessWarnThrottle();
@@ -351,13 +367,31 @@ test("throttle: warns once per staleness window and again after it elapses; rese
351
367
  assert.equal(t.maybeWarn(degraded, WINDOW, NOW + WINDOW + 2), true, "recovery reset lets a fresh episode warn at once");
352
368
  });
353
369
 
370
+ test("summarizeExtractionLiveness: fails closed for legacy root-store-only storage argument", async () => {
371
+ const storage = {
372
+ readMetadata: async () => ({ lastExtractionAt: "2026-07-29T10:00:00.000Z" }),
373
+ };
374
+ const doctorCheck = await summarizeExtractionLiveness(
375
+ { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
376
+ storage,
377
+ undefined,
378
+ NOW,
379
+ );
380
+ assert.equal(doctorCheck.status, "warn");
381
+ assert.ok(doctorCheck.summary.includes("aggregate watermark unavailable"));
382
+ });
383
+
354
384
  // ── renderExtractionLivenessStats ────────────────────────────────────────────
355
385
 
356
386
  test("renderExtractionLivenessStats: emits watermark, backlog, and a degraded verdict", async () => {
387
+ let rootReads = 0;
357
388
  const orchestrator = {
358
389
  config: { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
359
390
  storage: {
360
- loadMeta: async () => ({ extractionCount: 7, lastExtractionAt: ancient, lastConsolidationAt: null }),
391
+ loadMeta: async () => {
392
+ rootReads += 1;
393
+ return { extractionCount: 7, lastExtractionAt: ancient, lastConsolidationAt: null };
394
+ },
361
395
  },
362
396
  buffer: {
363
397
  getBufferSnapshot: async (): Promise<ExtractionBufferSnapshot> => ({
@@ -367,14 +401,32 @@ test("renderExtractionLivenessStats: emits watermark, backlog, and a degraded ve
367
401
  }),
368
402
  },
369
403
  };
370
- const lines = await renderExtractionLivenessStats(orchestrator, NOW);
404
+ const lines = await renderExtractionLivenessStats(
405
+ orchestrator,
406
+ {
407
+ lastExtractionAt: ancient,
408
+ readFailed: false,
409
+ rootStats: { extractionCount: 7, lastConsolidationAt: null },
410
+ },
411
+ NOW,
412
+ );
371
413
  assert.ok(lines.includes("Extractions: 7"), "reports extraction count");
414
+ assert.equal(rootReads, 0, "rendering reuses the aggregate reader's root metadata");
372
415
  assert.ok(lines.includes("Buffered sessions: 2 (5 turns pending)"), "reports backlog");
373
- assert.ok(lines.includes("Extraction watermark scope: root-store"), "reports the watermark scope");
416
+ assert.ok(lines.includes("Extraction watermark scope: aggregate"), "reports the watermark scope");
374
417
  assert.ok(
375
418
  lines.some((l) => l.startsWith("Extraction liveness: DEGRADED")),
376
419
  "reports a degraded verdict when the watermark is stale",
377
420
  );
421
+ const warmingLines = await renderExtractionLivenessStats(
422
+ orchestrator,
423
+ { lastExtractionAt: null, readFailed: false, pending: true },
424
+ NOW,
425
+ );
426
+ assert.ok(
427
+ warmingLines.some((line) => line === "Extraction liveness: ok"),
428
+ "cache warming is pending rather than degraded",
429
+ );
378
430
  });
379
431
 
380
432
  test("renderExtractionLivenessStats: reports DEGRADED when the buffer read fails (§22)", async () => {
@@ -393,7 +445,11 @@ test("renderExtractionLivenessStats: reports DEGRADED when the buffer read fails
393
445
  },
394
446
  },
395
447
  };
396
- const lines = await renderExtractionLivenessStats(orchestrator, NOW);
448
+ const lines = await renderExtractionLivenessStats(
449
+ orchestrator,
450
+ { lastExtractionAt: new Date(NOW).toISOString(), readFailed: false },
451
+ NOW,
452
+ );
397
453
  const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
398
454
  assert.ok(verdict, "emits a liveness verdict line");
399
455
  assert.match(verdict, /DEGRADED/);
@@ -418,7 +474,11 @@ test("renderExtractionLivenessStats: reports DEGRADED + unavailable counts when
418
474
  }),
419
475
  },
420
476
  };
421
- const lines = await renderExtractionLivenessStats(orchestrator, NOW);
477
+ const lines = await renderExtractionLivenessStats(
478
+ orchestrator,
479
+ { lastExtractionAt: null, readFailed: true, readError: "meta.json corrupt" },
480
+ NOW,
481
+ );
422
482
  assert.ok(lines.includes("Extractions: unavailable"), "meta counts read as unavailable, not 0/never");
423
483
  const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
424
484
  assert.match(verdict ?? "", /DEGRADED/);
@@ -448,12 +508,14 @@ test("resolveExtractionLivenessConfig: absent or partial block defaults to enabl
448
508
  });
449
509
 
450
510
  test("summarizeExtractionLiveness: no throw when the extractionLiveness block is absent or partial (host/legacy config)", async () => {
451
- const storage = { loadMeta: async () => ({ lastExtractionAt: null }) };
452
511
  // A PluginConfig built directly (host adapter / older persisted config) can omit
453
512
  // the block or leave it partial; every liveness surface must return a sane status
454
513
  // rather than crash (#2155 review).
455
514
  for (const config of [{}, { extractionLiveness: {} }, { extractionLiveness: { enabled: true } }]) {
456
- const check = await summarizeExtractionLiveness(config as never, storage);
515
+ const check = await summarizeExtractionLiveness(
516
+ config as never,
517
+ { lastExtractionAt: null, readFailed: false },
518
+ );
457
519
  assert.equal(check.key, "extraction_liveness");
458
520
  assert.ok(check.status === "ok" || check.status === "warn", `sane status for ${JSON.stringify(config)}`);
459
521
  assert.ok(!check.summary.includes("liveness check disabled"), "missing/partial block reads as enabled default");
@@ -21,7 +21,7 @@
21
21
  import { coerceBool, coerceNumber } from "./connectors/coerce.js";
22
22
  import { log } from "./logger.js";
23
23
  import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
24
- import type { MetaState, PluginConfig } from "./types.js";
24
+ import type { PluginConfig } from "./types.js";
25
25
 
26
26
  /** Default staleness window: 24h. A watermark older than this with a non-empty buffer is degraded. */
27
27
  const DEFAULT_STALE_WINDOW_MS = 86_400_000;
@@ -60,23 +60,57 @@ export interface ExtractionBufferSource {
60
60
  getBufferSnapshot(): Promise<ExtractionBufferSnapshot>;
61
61
  }
62
62
 
63
+ export interface ExtractionRootStats {
64
+ extractionCount?: number;
65
+ lastConsolidationAt?: string | null;
66
+ }
67
+
68
+ export interface ExtractionWatermarkRead {
69
+ lastExtractionAt: string | null;
70
+ readFailed: boolean;
71
+ readError?: string;
72
+ pending?: boolean;
73
+ rootStats?: ExtractionRootStats;
74
+ }
75
+
76
+ export type ExtractionWatermarkOrStorage =
77
+ | ExtractionWatermarkRead
78
+ | { readMetadata(): Promise<{ lastExtractionAt?: string | null } | null> }
79
+ | { loadMeta(): Promise<{ lastExtractionAt?: string | null }> };
80
+
81
+ export async function coerceExtractionWatermark(
82
+ watermark: ExtractionWatermarkOrStorage,
83
+ ): Promise<ExtractionWatermarkRead> {
84
+ if (watermark && "readFailed" in watermark && typeof watermark.readFailed === "boolean") {
85
+ return watermark as ExtractionWatermarkRead;
86
+ }
87
+ try {
88
+ let lastExtractionAt: string | null = null;
89
+ if ("loadMeta" in watermark && typeof watermark.loadMeta === "function") {
90
+ const meta = await watermark.loadMeta();
91
+ lastExtractionAt = meta?.lastExtractionAt ?? null;
92
+ } else if ("readMetadata" in watermark && typeof watermark.readMetadata === "function") {
93
+ const meta = await watermark.readMetadata();
94
+ lastExtractionAt = meta?.lastExtractionAt ?? null;
95
+ }
96
+ return {
97
+ lastExtractionAt,
98
+ readFailed: true,
99
+ readError: "aggregate watermark unavailable from legacy root-store-only storage argument",
100
+ };
101
+ } catch (error) {
102
+ return {
103
+ lastExtractionAt: null,
104
+ readFailed: true,
105
+ readError: error instanceof Error ? error.message : String(error),
106
+ };
107
+ }
108
+ }
109
+
63
110
  /**
64
- * The liveness payload surfaced on `/health.extraction` and in the doctor/stats
65
- * details.
66
- *
67
- * DAEMON-SCOPED SIGNAL: a daemon exposes a single extraction verdict; the
68
- * last-extraction watermark is read from the daemon's default/root store, so
69
- * `/health` returns the same `extraction` block for every namespace argument.
70
- *
71
- * KNOWN LIMITATION (issue #2159): extraction stamps `lastExtractionAt` on the
72
- * PER-NAMESPACE store (`storageFor(selfNamespace)`), so this watermark reflects
73
- * only extraction that stamped the root store (default-namespace work). On a
74
- * single-namespace deployment (the common case) the watermark and buffer share
75
- * one scope and the verdict is exact. On a multi-namespace deployment the verdict
76
- * is NOT guaranteed correct for work isolated to a non-default namespace: it can
77
- * over-report `degraded` (the default namespace idle while another is extracting)
78
- * or miss a stall confined to a non-default namespace. Aggregating the watermark
79
- * across namespace stores is tracked in #2159.
111
+ * The daemon-scoped liveness payload surfaced on `/health.extraction` and in
112
+ * doctor/stats details. Its watermark is the maximum successful extraction
113
+ * across every distinct namespace store, matching the daemon-wide buffer scope.
80
114
  */
81
115
  export interface ExtractionLivenessStatus {
82
116
  lastExtractionAt: string | null;
@@ -85,18 +119,8 @@ export interface ExtractionLivenessStatus {
85
119
  oldestBufferedTurnAgeMs: number | null;
86
120
  degraded: boolean;
87
121
  degradedReason: string | null;
88
- /**
89
- * Scope of the last-extraction watermark: `root-store` today (read from the
90
- * daemon's default/root store only), `aggregate` once it is resolved across
91
- * every namespace store (#2159). A monitor can alert on
92
- * `watermarkScope !== "aggregate"` while namespaces are enabled instead of
93
- * trusting a number that may be wrong in either direction.
94
- */
95
- watermarkScope: "root-store" | "aggregate";
96
- }
97
-
98
- interface ExtractionLivenessStorageLike {
99
- loadMeta(): Promise<Pick<MetaState, "lastExtractionAt">>;
122
+ watermarkPending?: boolean;
123
+ watermarkScope: "aggregate";
100
124
  }
101
125
 
102
126
  interface ExtractionLivenessOrchestratorLike {
@@ -190,7 +214,7 @@ export function evaluateExtractionLiveness(input: {
190
214
  nowMs: number;
191
215
  metaReadFailed?: boolean;
192
216
  metaReadError?: string;
193
- watermarkScope?: "root-store" | "aggregate";
217
+ watermarkPending?: boolean;
194
218
  }): ExtractionLivenessStatus {
195
219
  const { config, lastExtractionAt, snapshot, nowMs } = input;
196
220
  const lastMs = lastExtractionAt !== null ? Date.parse(lastExtractionAt) : Number.NaN;
@@ -204,8 +228,10 @@ export function evaluateExtractionLiveness(input: {
204
228
  // (§22): a storage/pipeline outage must not read as healthy, and must stay
205
229
  // distinct from a pipeline that has genuinely never extracted.
206
230
  const metaReadFailed = input.metaReadFailed === true;
231
+ const watermarkPending = input.watermarkPending === true;
207
232
  const readFailed = snapshot.readFailed === true;
208
- const degraded = config.enabled && (metaReadFailed || readFailed || (hasBacklog && stale));
233
+ const degraded =
234
+ config.enabled && (metaReadFailed || readFailed || (!watermarkPending && hasBacklog && stale));
209
235
 
210
236
  let degradedReason: string | null = null;
211
237
  if (degraded) {
@@ -230,7 +256,8 @@ export function evaluateExtractionLiveness(input: {
230
256
  oldestBufferedTurnAgeMs: ageMsFrom(snapshot.oldestTurnTimestamp, nowMs),
231
257
  degraded,
232
258
  degradedReason,
233
- watermarkScope: input.watermarkScope ?? "root-store",
259
+ ...(watermarkPending ? { watermarkPending: true } : {}),
260
+ watermarkScope: "aggregate",
234
261
  };
235
262
  }
236
263
 
@@ -262,40 +289,27 @@ async function readBufferSnapshot(
262
289
  }
263
290
 
264
291
  /**
265
- * Gather the liveness status from a meta store (last-extraction watermark) and
266
- * an optional buffer (backlog). A read failure on EITHER side is captured and
267
- * surfaced as a DEGRADED status with a distinct reason (§22) rather than being
268
- * swallowed into a healthy-looking verdict; no failure path throws.
292
+ * Gather the liveness status from an aggregate watermark read and an optional
293
+ * buffer. A read failure on either side is surfaced as a degraded status with a
294
+ * distinct reason rather than swallowed into a healthy-looking verdict.
269
295
  */
270
296
  export async function gatherExtractionLivenessStatus(input: {
271
297
  config: ExtractionLivenessConfig;
272
- storage: ExtractionLivenessStorageLike;
298
+ watermark: ExtractionWatermarkOrStorage;
273
299
  buffer: ExtractionBufferSource | undefined;
274
300
  nowMs: number;
275
301
  }): Promise<ExtractionLivenessStatus> {
276
- const { config, storage, buffer, nowMs } = input;
277
- let lastExtractionAt: string | null = null;
278
- let metaReadFailed = false;
279
- let metaReadError: string | undefined;
280
- try {
281
- lastExtractionAt = (await storage.loadMeta()).lastExtractionAt ?? null;
282
- } catch (err) {
283
- // A swallowed loadMeta() failure that reads as "never extracted" is the
284
- // exact §22 conflation this feature exists to kill — surface it explicitly.
285
- metaReadFailed = true;
286
- metaReadError = err instanceof Error ? err.message : String(err);
287
- }
302
+ const { config, buffer, nowMs } = input;
303
+ const watermarkRead = await coerceExtractionWatermark(input.watermark);
288
304
  const snapshot = await readBufferSnapshot(buffer);
289
305
  return evaluateExtractionLiveness({
290
306
  config,
291
- lastExtractionAt,
307
+ lastExtractionAt: watermarkRead.lastExtractionAt,
292
308
  snapshot,
293
309
  nowMs,
294
- metaReadFailed,
295
- metaReadError,
296
- // Today the watermark comes from the root store only; #2159 flips this to
297
- // "aggregate" when the resolver reads across every namespace store.
298
- watermarkScope: "root-store",
310
+ metaReadFailed: watermarkRead.readFailed,
311
+ metaReadError: watermarkRead.readError,
312
+ watermarkPending: watermarkRead.pending,
299
313
  });
300
314
  }
301
315
 
@@ -349,25 +363,19 @@ export function resolveExtractionLivenessConfig(
349
363
  }
350
364
 
351
365
  /**
352
- * Compute the liveness status for the authenticated `/health` payload and emit
353
- * the throttled aggregated WARN.
354
- *
355
- * The watermark is read from the daemon's default/root store (`orchestrator.storage`),
356
- * which is why `/health` returns the same `extraction` block for any namespace
357
- * argument. KNOWN LIMITATION (issue #2159): extraction writes `lastExtractionAt`
358
- * per-namespace, so this watermark reflects only default-namespace extraction and
359
- * the verdict is exact only for a single-namespace daemon (see
360
- * `ExtractionLivenessStatus`).
366
+ * Compute the daemon-wide liveness status for the authenticated `/health`
367
+ * payload and emit the throttled aggregate warning.
361
368
  */
362
369
  export async function computeExtractionLivenessStatus(
363
- orchestrator: ExtractionLivenessOrchestratorLike & { storage: ExtractionLivenessStorageLike },
370
+ orchestrator: ExtractionLivenessOrchestratorLike,
371
+ watermark: ExtractionWatermarkOrStorage,
364
372
  throttle?: ExtractionLivenessWarnThrottle,
365
373
  nowMs: number = Date.now(),
366
374
  ): Promise<ExtractionLivenessStatus> {
367
375
  const config = resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness);
368
376
  const status = await gatherExtractionLivenessStatus({
369
377
  config,
370
- storage: orchestrator.storage,
378
+ watermark,
371
379
  buffer: orchestrator.buffer,
372
380
  nowMs,
373
381
  });
@@ -381,22 +389,18 @@ export async function computeExtractionLivenessStatus(
381
389
  */
382
390
  export async function summarizeExtractionLiveness(
383
391
  config: Pick<PluginConfig, "extractionLiveness">,
384
- storage: ExtractionLivenessStorageLike,
392
+ watermark: ExtractionWatermarkOrStorage,
385
393
  buffer?: ExtractionBufferSource,
386
394
  nowMs: number = Date.now(),
387
395
  ): Promise<OperatorDoctorCheck> {
388
396
  const livenessConfig = resolveExtractionLivenessConfig(config.extractionLiveness);
389
397
  const status = await gatherExtractionLivenessStatus({
390
398
  config: livenessConfig,
391
- storage,
399
+ watermark,
392
400
  buffer,
393
401
  nowMs,
394
402
  });
395
- const scopeNote =
396
- ` Watermark scope: ${status.watermarkScope}` +
397
- (status.watermarkScope === "root-store"
398
- ? " (default-namespace only; multi-namespace aggregation tracked in #2159)."
399
- : ".");
403
+ const scopeNote = ` Watermark scope: ${status.watermarkScope}.`;
400
404
  const summary =
401
405
  (status.degraded
402
406
  ? `Extraction pipeline degraded: ${status.degradedReason}.`
@@ -416,44 +420,38 @@ export async function summarizeExtractionLiveness(
416
420
  };
417
421
  }
418
422
 
419
- /** Meta fields the `remnic stats` view renders directly, beyond the liveness watermark. */
420
- type ExtractionStatsMeta = Pick<MetaState, "extractionCount" | "lastExtractionAt" | "lastConsolidationAt">;
421
-
422
423
  /**
423
- * Lines for `remnic stats`: extraction watermark, buffer backlog, and the
424
- * liveness verdict so dashboards can alert on a stalled pipeline. The meta
425
- * watermark is loaded HERE (not by the caller) so a meta-read failure surfaces
426
- * as an explicit DEGRADED verdict plus "unavailable" counts (§22) instead of
427
- * crashing the whole `stats` command.
424
+ * Lines for `remnic stats`: the aggregate extraction watermark, root metadata
425
+ * counters, buffer backlog, and liveness verdict. Root counter failures render
426
+ * those counters unavailable; the injected aggregate read alone determines
427
+ * watermark liveness so stats cannot diverge from health and doctor.
428
428
  */
429
429
  export async function renderExtractionLivenessStats(
430
- orchestrator: ExtractionLivenessOrchestratorLike & { storage: { loadMeta(): Promise<ExtractionStatsMeta> } },
430
+ orchestrator: ExtractionLivenessOrchestratorLike,
431
+ watermark: ExtractionWatermarkRead,
431
432
  nowMs: number = Date.now(),
432
433
  ): Promise<string[]> {
433
- let meta: ExtractionStatsMeta | null = null;
434
- let metaReadFailed = false;
435
- let metaReadError: string | undefined;
436
- try {
437
- meta = await orchestrator.storage.loadMeta();
438
- } catch (err) {
439
- metaReadFailed = true;
440
- metaReadError = err instanceof Error ? err.message : String(err);
441
- }
442
434
  const status = evaluateExtractionLiveness({
443
435
  config: resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness),
444
- lastExtractionAt: meta?.lastExtractionAt ?? null,
436
+ lastExtractionAt: watermark.lastExtractionAt,
445
437
  snapshot: await readBufferSnapshot(orchestrator.buffer),
446
438
  nowMs,
447
- metaReadFailed,
448
- metaReadError,
439
+ metaReadFailed: watermark.readFailed,
440
+ metaReadError: watermark.readError,
441
+ watermarkPending: watermark.pending,
449
442
  });
450
443
  const oldestAge =
451
444
  status.oldestBufferedTurnAgeMs !== null ? formatAgeMs(status.oldestBufferedTurnAgeMs) : "n/a";
452
- const unavailable = metaReadFailed ? "unavailable" : "never";
445
+ const rootStats = watermark.rootStats;
446
+ const watermarkDisplay = watermark.readFailed
447
+ ? "unavailable"
448
+ : watermark.pending
449
+ ? "pending"
450
+ : status.lastExtractionAt ?? "never";
453
451
  return [
454
- `Extractions: ${meta?.extractionCount ?? unavailable}`,
455
- `Last extraction: ${meta?.lastExtractionAt ?? unavailable}`,
456
- `Last consolidation: ${meta?.lastConsolidationAt ?? unavailable}`,
452
+ `Extractions: ${rootStats?.extractionCount ?? "unavailable"}`,
453
+ `Last extraction: ${watermarkDisplay}`,
454
+ `Last consolidation: ${rootStats ? rootStats.lastConsolidationAt ?? "never" : "unavailable"}`,
457
455
  `Buffered sessions: ${status.bufferedSessionCount} (${status.pendingTurnCount} turns pending)`,
458
456
  `Oldest buffered turn age: ${oldestAge}`,
459
457
  `Extraction watermark scope: ${status.watermarkScope}`,