@remnic/core 9.40.0 → 9.41.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 (275) 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 +10 -10
  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-service-B2h6rITp.d.ts → access-service-D1BkxwSf.d.ts} +7 -7
  38. package/dist/access-service-helpers.d.ts +10 -10
  39. package/dist/access-service.d.ts +10 -10
  40. package/dist/access-service.js +5 -4
  41. package/dist/access-surface-catalog.d.ts +10 -10
  42. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  43. package/dist/action-confidence.d.ts +1 -1
  44. package/dist/active-memory-bridge.d.ts +1 -1
  45. package/dist/active-recall.d.ts +1 -1
  46. package/dist/active-recall.js +2 -2
  47. package/dist/behavior-learner.d.ts +1 -1
  48. package/dist/behavior-signals.d.ts +1 -1
  49. package/dist/bootstrap.d.ts +8 -8
  50. package/dist/briefing.d.ts +2 -2
  51. package/dist/buffer-surprise-report.d.ts +1 -1
  52. package/dist/buffer-turn-helpers.d.ts +1 -1
  53. package/dist/buffer.d.ts +2 -2
  54. package/dist/bulk-import/index.d.ts +3 -3
  55. package/dist/calibration.d.ts +1 -1
  56. package/dist/capabilities.d.ts +1 -1
  57. package/dist/{catalog-Bz2LxGNb.d.ts → catalog-DPQynQZG.d.ts} +1 -1
  58. package/dist/causal-behavior.d.ts +1 -1
  59. package/dist/causal-consolidation.d.ts +1 -1
  60. package/dist/causal-trajectory-graph.d.ts +1 -1
  61. package/dist/{chunk-443XCR34.js → chunk-3SG4FGDV.js} +53 -41
  62. package/dist/chunk-3SG4FGDV.js.map +1 -0
  63. package/dist/{chunk-XPQ5ZTL6.js → chunk-6I2C3EOH.js} +6 -6
  64. package/dist/chunk-7VIB7PAE.js +142 -0
  65. package/dist/chunk-7VIB7PAE.js.map +1 -0
  66. package/dist/{chunk-AYICY7IE.js → chunk-AXUU7SG3.js} +2 -2
  67. package/dist/{chunk-RK6WMGTX.js → chunk-CQ5MR2HQ.js} +13 -9
  68. package/dist/chunk-CQ5MR2HQ.js.map +1 -0
  69. package/dist/{chunk-KW4ZFTUS.js → chunk-CWPDM66T.js} +5 -5
  70. package/dist/{chunk-NEO4QCID.js → chunk-FHEJ22B7.js} +10 -4
  71. package/dist/chunk-FHEJ22B7.js.map +1 -0
  72. package/dist/{chunk-EBWOOJEP.js → chunk-GDC4INBF.js} +2 -2
  73. package/dist/{chunk-UIVRPXRR.js → chunk-KXGN6G2Y.js} +3 -3
  74. package/dist/chunk-KXGN6G2Y.js.map +1 -0
  75. package/dist/{chunk-AXHMCRS3.js → chunk-OYFDEJZB.js} +23 -6
  76. package/dist/chunk-OYFDEJZB.js.map +1 -0
  77. package/dist/{chunk-SD4A6IZY.js → chunk-PWKZV4A3.js} +2 -2
  78. package/dist/{chunk-XYXYEDPL.js → chunk-Q4XL7LJW.js} +26 -5
  79. package/dist/chunk-Q4XL7LJW.js.map +1 -0
  80. package/dist/{chunk-MOHZIKZM.js → chunk-TM72UOX2.js} +2 -2
  81. package/dist/{chunk-GLBK35BT.js → chunk-UGCUVLOL.js} +14 -10
  82. package/dist/chunk-UGCUVLOL.js.map +1 -0
  83. package/dist/{chunk-Q6QYN56R.js → chunk-UQVGDEAJ.js} +2 -2
  84. package/dist/{chunk-KYYRJVE6.js → chunk-XRWVXPEM.js} +2 -2
  85. package/dist/{cli-CO8qKPM5.d.ts → cli-IUhV-lmk.d.ts} +5 -5
  86. package/dist/cli.d.ts +12 -12
  87. package/dist/cli.js +15 -14
  88. package/dist/compounding/engine.d.ts +2 -2
  89. package/dist/compounding/preference-consolidator.d.ts +1 -1
  90. package/dist/compression-optimizer.d.ts +1 -1
  91. package/dist/config.d.ts +1 -1
  92. package/dist/config.js +2 -2
  93. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  94. package/dist/connectors/codex-materialize.d.ts +1 -1
  95. package/dist/connectors/index.d.ts +1 -1
  96. package/dist/consolidation-provenance-check.d.ts +2 -2
  97. package/dist/consolidation-undo.d.ts +2 -2
  98. package/dist/contradiction/index.d.ts +2 -2
  99. package/dist/conversation-index/backend.d.ts +1 -1
  100. package/dist/conversation-index/chunker.d.ts +1 -1
  101. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  102. package/dist/conversation-index/indexer.d.ts +1 -1
  103. package/dist/conversation-index/search.d.ts +1 -1
  104. package/dist/corpus-watermark.d.ts +7 -1
  105. package/dist/corpus-watermark.js +2 -2
  106. package/dist/day-summary.d.ts +1 -1
  107. package/dist/delinearize.d.ts +1 -1
  108. package/dist/direct-answer-wiring.d.ts +1 -1
  109. package/dist/direct-answer.d.ts +1 -1
  110. package/dist/embedding-fallback.d.ts +1 -1
  111. package/dist/enrichment/index.d.ts +1 -1
  112. package/dist/entity-retrieval.d.ts +2 -2
  113. package/dist/entity-schema.d.ts +1 -1
  114. package/dist/explicit-capture.d.ts +8 -8
  115. package/dist/extraction-error-classification.d.ts +1 -1
  116. package/dist/extraction-faithfulness.d.ts +1 -1
  117. package/dist/extraction-judge-telemetry.d.ts +1 -1
  118. package/dist/extraction-judge-training.d.ts +1 -1
  119. package/dist/extraction-judge.d.ts +1 -1
  120. package/dist/extraction-liveness.d.ts +1 -1
  121. package/dist/extraction-liveness.js +3 -1
  122. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  123. package/dist/extraction-source-grounding.d.ts +1 -1
  124. package/dist/extraction.d.ts +1 -1
  125. package/dist/fallback-llm.d.ts +1 -1
  126. package/dist/graph-dashboard-diff.d.ts +1 -1
  127. package/dist/graph-dashboard-key.d.ts +1 -1
  128. package/dist/graph-dashboard-parser.d.ts +1 -1
  129. package/dist/graph-edge-reinforcement.d.ts +1 -1
  130. package/dist/graph-snapshot.d.ts +1 -1
  131. package/dist/graph.d.ts +1 -1
  132. package/dist/importance.d.ts +1 -1
  133. package/dist/importers/index.d.ts +1 -1
  134. package/dist/in-flight-reads.d.ts +1 -1
  135. package/dist/index.d.ts +19 -19
  136. package/dist/index.js +16 -15
  137. package/dist/intent.d.ts +1 -1
  138. package/dist/lcm/engine.d.ts +1 -1
  139. package/dist/lcm/index.d.ts +1 -1
  140. package/dist/lcm/tools.d.ts +1 -1
  141. package/dist/lifecycle.d.ts +1 -1
  142. package/dist/live-connectors-runner.d.ts +1 -1
  143. package/dist/local-llm.d.ts +1 -1
  144. package/dist/local-model-endpoint.d.ts +1 -1
  145. package/dist/maintenance/memory-governance.d.ts +1 -1
  146. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  147. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  148. package/dist/{maintenance-D2jkiT9r.d.ts → maintenance-DVBLXkFw.d.ts} +3 -3
  149. package/dist/mcp-memory-inspector-app.d.ts +10 -10
  150. package/dist/memory-action-policy.d.ts +1 -1
  151. package/dist/memory-cache.d.ts +1 -1
  152. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  153. package/dist/memory-projection-store.d.ts +1 -1
  154. package/dist/memory-provenance.d.ts +1 -1
  155. package/dist/memory-worth-outcomes.d.ts +2 -2
  156. package/dist/models-json.d.ts +1 -1
  157. package/dist/namespaces/migrate.d.ts +4 -3
  158. package/dist/namespaces/migrate.js +1 -1
  159. package/dist/namespaces/principal.d.ts +1 -1
  160. package/dist/namespaces/search.d.ts +1 -1
  161. package/dist/namespaces/storage.d.ts +3 -3
  162. package/dist/native-knowledge.d.ts +1 -1
  163. package/dist/offline-sync-impression-drain.d.ts +1 -1
  164. package/dist/operator-doctor-corpus.d.ts +1 -1
  165. package/dist/operator-doctor-corpus.js +3 -3
  166. package/dist/operator-doctor-replica.d.ts +1 -1
  167. package/dist/operator-toolkit.d.ts +5 -4
  168. package/dist/operator-toolkit.js +7 -6
  169. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  170. package/dist/orchestration/maintenance.d.ts +4 -4
  171. package/dist/{orchestrator-SHaRXCUF.d.ts → orchestrator-fYTncnLy.d.ts} +7 -7
  172. package/dist/orchestrator.d.ts +8 -8
  173. package/dist/orchestrator.js +16 -15
  174. package/dist/patterns-cli.d.ts +1 -1
  175. package/dist/{pipeline-B5_tS1Yc.d.ts → pipeline-Cs76XC-p.d.ts} +1 -1
  176. package/dist/policy-runtime.d.ts +1 -1
  177. package/dist/proactive-contention.d.ts +1 -1
  178. package/dist/provenance.d.ts +1 -1
  179. package/dist/{qmd-CkEV_SPG.d.ts → qmd-WJwvBgiF.d.ts} +1 -1
  180. package/dist/qmd-preflight.d.ts +2 -2
  181. package/dist/qmd-recall-cache.d.ts +1 -1
  182. package/dist/qmd.d.ts +2 -2
  183. package/dist/recall-concurrency-config.d.ts +1 -1
  184. package/dist/recall-disclosure-escalation.d.ts +1 -1
  185. package/dist/recall-explain-renderer.d.ts +1 -1
  186. package/dist/recall-memory-map.d.ts +1 -1
  187. package/dist/recall-planner-llm.d.ts +1 -1
  188. package/dist/recall-state.d.ts +1 -1
  189. package/dist/recall-tag-filter.d.ts +1 -1
  190. package/dist/recall-timings.d.ts +1 -1
  191. package/dist/recall-xray-cli.d.ts +1 -1
  192. package/dist/recall-xray-renderer.d.ts +1 -1
  193. package/dist/recall-xray.d.ts +1 -1
  194. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  195. package/dist/replay/normalizers/claude.d.ts +1 -1
  196. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  197. package/dist/replay/normalizers/shared.d.ts +1 -1
  198. package/dist/replay/runner.d.ts +1 -1
  199. package/dist/replay/types.d.ts +1 -1
  200. package/dist/replica-divergence.d.ts +1 -1
  201. package/dist/replica-peers-config.d.ts +1 -1
  202. package/dist/resolve-auth-token.d.ts +1 -1
  203. package/dist/resume-bundles.js +3 -3
  204. package/dist/retrieval-agents.d.ts +2 -2
  205. package/dist/retrieval-tiers.d.ts +1 -1
  206. package/dist/routing/engine.d.ts +1 -1
  207. package/dist/routing/store.d.ts +1 -1
  208. package/dist/salvage-envelope.d.ts +1 -1
  209. package/dist/{scope-profiles-DeAdnocS.d.ts → scope-profiles-CQ0NAleS.d.ts} +1 -1
  210. package/dist/search/embed-helper.d.ts +1 -1
  211. package/dist/search/factory.d.ts +1 -1
  212. package/dist/search/index.d.ts +1 -1
  213. package/dist/search/lancedb-backend.d.ts +1 -1
  214. package/dist/search/meilisearch-backend.d.ts +1 -1
  215. package/dist/search/noop-backend.d.ts +1 -1
  216. package/dist/search/orama-backend.d.ts +1 -1
  217. package/dist/search/port.d.ts +1 -1
  218. package/dist/search/remote-backend.d.ts +1 -1
  219. package/dist/{semantic-consolidation-DR_h-JUD.d.ts → semantic-consolidation-BWxOBMtz.d.ts} +1 -1
  220. package/dist/semantic-consolidation.d.ts +2 -2
  221. package/dist/semantic-rule-verifier.d.ts +1 -1
  222. package/dist/{service-BTb94UPJ.d.ts → service-CLuMO-pa.d.ts} +2 -2
  223. package/dist/session-observer-bands.d.ts +1 -1
  224. package/dist/session-observer-state.d.ts +1 -1
  225. package/dist/shared-context/manager.d.ts +1 -1
  226. package/dist/signal.d.ts +1 -1
  227. package/dist/source-agent-qualifier.d.ts +1 -1
  228. package/dist/{storage-ax2EIsVS.d.ts → storage-Blr71GpS.d.ts} +1 -1
  229. package/dist/storage.d.ts +2 -2
  230. package/dist/summarizer.d.ts +1 -1
  231. package/dist/summary-snapshot.d.ts +1 -1
  232. package/dist/temporal-supersession.d.ts +2 -2
  233. package/dist/temporal-timeline-recall.d.ts +1 -1
  234. package/dist/temporal-validity.d.ts +1 -1
  235. package/dist/threading.d.ts +1 -1
  236. package/dist/tier-migration.d.ts +2 -2
  237. package/dist/tier-routing.d.ts +1 -1
  238. package/dist/topics.d.ts +1 -1
  239. package/dist/transcript.d.ts +1 -1
  240. package/dist/trust-score-stage.d.ts +1 -1
  241. package/dist/trust-score.d.ts +1 -1
  242. package/dist/{types-CCGL-9NX.d.ts → types-CqWwMPr_.d.ts} +41 -59
  243. package/dist/types.d.ts +1 -1
  244. package/dist/utility-runtime.d.ts +1 -1
  245. package/dist/write-envelope.d.ts +1 -1
  246. package/package.json +2 -2
  247. package/src/access-http.test.ts +2 -2
  248. package/src/access-service-health.test.ts +39 -17
  249. package/src/access-service.ts +25 -3
  250. package/src/cli.ts +3 -3
  251. package/src/corpus-watermark.test.ts +16 -0
  252. package/src/corpus-watermark.ts +28 -5
  253. package/src/extraction-liveness.test.ts +81 -19
  254. package/src/extraction-liveness.ts +96 -98
  255. package/src/extraction-watermark.test.ts +290 -0
  256. package/src/namespaces/migrate.ts +10 -2
  257. package/src/operator-doctor-corpus.test.ts +7 -0
  258. package/src/operator-doctor-corpus.ts +1 -1
  259. package/src/operator-toolkit.ts +8 -8
  260. package/src/orchestration/extraction-watermark.ts +188 -0
  261. package/dist/chunk-443XCR34.js.map +0 -1
  262. package/dist/chunk-AXHMCRS3.js.map +0 -1
  263. package/dist/chunk-GLBK35BT.js.map +0 -1
  264. package/dist/chunk-NEO4QCID.js.map +0 -1
  265. package/dist/chunk-RK6WMGTX.js.map +0 -1
  266. package/dist/chunk-UIVRPXRR.js.map +0 -1
  267. package/dist/chunk-XYXYEDPL.js.map +0 -1
  268. /package/dist/{chunk-XPQ5ZTL6.js.map → chunk-6I2C3EOH.js.map} +0 -0
  269. /package/dist/{chunk-AYICY7IE.js.map → chunk-AXUU7SG3.js.map} +0 -0
  270. /package/dist/{chunk-KW4ZFTUS.js.map → chunk-CWPDM66T.js.map} +0 -0
  271. /package/dist/{chunk-EBWOOJEP.js.map → chunk-GDC4INBF.js.map} +0 -0
  272. /package/dist/{chunk-SD4A6IZY.js.map → chunk-PWKZV4A3.js.map} +0 -0
  273. /package/dist/{chunk-MOHZIKZM.js.map → chunk-TM72UOX2.js.map} +0 -0
  274. /package/dist/{chunk-Q6QYN56R.js.map → chunk-UQVGDEAJ.js.map} +0 -0
  275. /package/dist/{chunk-KYYRJVE6.js.map → chunk-XRWVXPEM.js.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { TierRoutingPolicy } from './tier-routing.js';
2
- import './types-CCGL-9NX.js';
2
+ import './types-CqWwMPr_.js';
3
3
  import './message-parts/index.js';
4
4
  import './bounded-jsonl-state.js';
5
5
  import './operator-doctor-types.js';
@@ -1,4 +1,4 @@
1
- import { v as MemoryCategory } from './types-CCGL-9NX.js';
1
+ import { v as MemoryCategory } from './types-CqWwMPr_.js';
2
2
  import './message-parts/index.js';
3
3
  import './bounded-jsonl-state.js';
4
4
  import './operator-doctor-types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.40.0",
3
+ "version": "9.41.0",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3086,7 +3086,7 @@
3086
3086
  "core"
3087
3087
  ],
3088
3088
  "peerDependencies": {
3089
- "@remnic/coding-graph": "^9.40.0"
3089
+ "@remnic/coding-graph": "^9.41.0"
3090
3090
  },
3091
3091
  "peerDependenciesMeta": {
3092
3092
  "@remnic/coding-graph": {
@@ -1993,7 +1993,7 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
1993
1993
  oldestBufferedTurnAgeMs: null,
1994
1994
  degraded: false,
1995
1995
  degradedReason: null,
1996
- watermarkScope: "root-store",
1996
+ watermarkScope: "aggregate",
1997
1997
  },
1998
1998
  corpus: [],
1999
1999
  corpusComplete: true,
@@ -2120,7 +2120,7 @@ test("HTTP authTokenEntriesGetter is authoritative: scope policy binds connector
2120
2120
  oldestBufferedTurnAgeMs: null,
2121
2121
  degraded: false,
2122
2122
  degradedReason: null,
2123
- watermarkScope: "root-store",
2123
+ watermarkScope: "aggregate",
2124
2124
  },
2125
2125
  corpus: [],
2126
2126
  corpusComplete: true,
@@ -1,11 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
6
 
7
7
  import { EngramAccessService } from "./access-service.js";
8
8
  import { parseConfig } from "./config.js";
9
+ import { TOKEN_CAPABILITIES_VERSION, tokenCapabilityStore } from "./access-token-capabilities.js";
9
10
  import { namespaceCollectionName } from "./namespaces/search.js";
10
11
  import type { Orchestrator } from "./orchestrator.js";
11
12
  import type { SearchBackend } from "./search/port.js";
@@ -548,16 +549,27 @@ test("health reports extraction liveness ok when the buffer is empty (nothing to
548
549
  assert.equal(health.extraction.degraded, false);
549
550
  assert.equal(health.extraction.bufferedSessionCount, 0);
550
551
  assert.equal(health.extraction.lastExtractionAt, oldTs);
552
+ const scopedHealth = await tokenCapabilityStore.run(
553
+ { version: TOKEN_CAPABILITIES_VERSION, namespaces: ["default"] },
554
+ () => service.health(),
555
+ );
556
+ assert.equal(scopedHealth.extraction.degraded, health.extraction.degraded);
557
+ assert.equal(scopedHealth.extraction.lastExtractionAt, null);
551
558
  } finally {
552
559
  await rm(memoryDir, { recursive: true, force: true });
553
560
  }
554
561
  });
555
562
 
556
- test("health returns the same daemon-global extraction verdict for every namespace (issue #2151)", async () => {
563
+ test("health returns one daemon-global aggregate extraction verdict for every namespace", async () => {
557
564
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-health-liveness-ns-"));
558
565
  try {
559
- const rootTs = new Date().toISOString(); // fresh daemon-global watermark
560
- const staleTs = new Date(Date.now() - 7_200_000).toISOString(); // 2h old
566
+ const namespaceDirs: Record<string, string> = {
567
+ fresh: path.join(memoryDir, "namespaces", "fresh"),
568
+ stale: path.join(memoryDir, "namespaces", "stale"),
569
+ };
570
+ await Promise.all(Object.values(namespaceDirs).map((dir) => mkdir(dir, { recursive: true })));
571
+ const freshTs = new Date().toISOString();
572
+ const staleTs = new Date(Date.now() - 7_200_000).toISOString();
561
573
  const config = parseConfig({
562
574
  memoryDir,
563
575
  qmdEnabled: false,
@@ -565,20 +577,25 @@ test("health returns the same daemon-global extraction verdict for every namespa
565
577
  defaultNamespace: "default",
566
578
  extractionLiveness: { staleWindowMs: 3_600_000 },
567
579
  });
568
- // Per-namespace stores return DIVERGING watermarks (fresh vs 2h stale vs
569
- // never), but the global buffer is shared and the watermark must be sourced
570
- // from the root store. Pre-fix, the extraction verdict tracked the
571
- // namespace argument; post-fix it is identical for every namespace.
572
- const perNamespaceMeta: Record<string, string | null> = { fresh: rootTs, stale: staleTs };
580
+ const perNamespaceMeta: Record<string, string | null> = {
581
+ default: staleTs,
582
+ fresh: freshTs,
583
+ stale: staleTs,
584
+ };
573
585
  const service = new EngramAccessService({
574
586
  config,
575
587
  qmd: makeQmd({}),
576
588
  buffer: {
577
- getBufferSnapshot: async () => ({ bufferedSessionCount: 3, pendingTurnCount: 12, oldestTurnTimestamp: staleTs }),
589
+ getBufferSnapshot: async () => ({
590
+ bufferedSessionCount: 3,
591
+ pendingTurnCount: 12,
592
+ oldestTurnTimestamp: staleTs,
593
+ }),
578
594
  },
579
595
  storage: {
596
+ dir: memoryDir,
580
597
  loadMeta: async () => ({
581
- lastExtractionAt: rootTs,
598
+ lastExtractionAt: staleTs,
582
599
  extractionCount: 4,
583
600
  lastConsolidationAt: null,
584
601
  totalMemories: 0,
@@ -586,10 +603,11 @@ test("health returns the same daemon-global extraction verdict for every namespa
586
603
  }),
587
604
  },
588
605
  async getStorage(namespace?: string) {
606
+ const resolved = namespace ?? "default";
589
607
  return {
590
- dir: memoryDir,
608
+ dir: namespaceDirs[resolved] ?? memoryDir,
591
609
  loadMeta: async () => ({
592
- lastExtractionAt: namespace ? perNamespaceMeta[namespace] ?? null : staleTs,
610
+ lastExtractionAt: perNamespaceMeta[resolved] ?? null,
593
611
  extractionCount: 4,
594
612
  lastConsolidationAt: null,
595
613
  totalMemories: 0,
@@ -599,19 +617,23 @@ test("health returns the same daemon-global extraction verdict for every namespa
599
617
  },
600
618
  } as unknown as Orchestrator);
601
619
 
620
+ await service.health();
621
+ const serviceWithCache = service as unknown as {
622
+ corpusWatermarkCache: { whenIdle(): Promise<void> };
623
+ };
624
+ await serviceWithCache.corpusWatermarkCache.whenIdle();
625
+
602
626
  const fresh = (await service.health("fresh")).extraction;
603
627
  const stale = (await service.health("stale")).extraction;
604
628
  const def = (await service.health()).extraction;
605
- // The namespace-governed fields must not vary with the namespace argument
606
- // (oldestBufferedTurnAgeMs is time-derived and intentionally excluded).
607
629
  assert.equal(stale.degraded, fresh.degraded, "degraded must not vary by namespace");
608
630
  assert.equal(def.degraded, fresh.degraded, "default health matches namespaced health");
609
631
  assert.equal(stale.lastExtractionAt, fresh.lastExtractionAt, "watermark must not vary by namespace");
610
632
  assert.equal(def.lastExtractionAt, fresh.lastExtractionAt);
611
633
  assert.equal(stale.degradedReason, fresh.degradedReason);
612
- // Sourced from the fresh root watermark → healthy despite stale per-namespace stores.
613
634
  assert.equal(fresh.degraded, false);
614
- assert.equal(fresh.lastExtractionAt, rootTs);
635
+ assert.equal(fresh.lastExtractionAt, freshTs);
636
+ assert.equal(fresh.watermarkScope, "aggregate");
615
637
  } finally {
616
638
  await rm(memoryDir, { recursive: true, force: true });
617
639
  }
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
7
7
  import { ZodError } from "zod";
8
8
  import { AccessIdempotencyStore, hashAccessIdempotencyPayload } from "./access-idempotency.js";
9
9
  import { computeExtractionLivenessStatus, ExtractionLivenessWarnThrottle } from "./extraction-liveness.js";
10
+ import { readAggregateExtractionWatermark } from "./orchestration/extraction-watermark.js";
10
11
  import { enforceNamespaceAllowList, tokenCapabilityStore } from "./access-token-capabilities.js";
11
12
  import {
12
13
  recordCitationUsage as recordCitationUsageForAccess,
@@ -2369,10 +2370,31 @@ export class EngramAccessService {
2369
2370
  const storage = await this.orchestrator.getStorage(resolvedNamespace);
2370
2371
  const searchBackend = this.orchestrator.config.searchBackend ?? "qmd";
2371
2372
  const qmdEnabled = resolveQmdCapabilities(this.orchestrator.config).qmd === true;
2372
- const extraction = await computeExtractionLivenessStatus(this.orchestrator, this.extractionLivenessWarn);
2373
+ const caps = tokenCapabilityStore.getStore();
2374
+ const extractionWatermark = await readAggregateExtractionWatermark({
2375
+ config: this.orchestrator.config,
2376
+ rootStorage: this.orchestrator.storage,
2377
+ storageForNamespace: (candidate) => this.orchestrator.getStorage(candidate),
2378
+ rootsCache: this.corpusWatermarkCache,
2379
+ });
2380
+ const extraction = await computeExtractionLivenessStatus(
2381
+ this.orchestrator,
2382
+ extractionWatermark,
2383
+ caps?.namespaces !== undefined ? undefined : this.extractionLivenessWarn,
2384
+ );
2385
+ const visibleExtraction =
2386
+ caps?.namespaces !== undefined
2387
+ ? {
2388
+ ...extraction,
2389
+ lastExtractionAt: null,
2390
+ bufferedSessionCount: 0,
2391
+ pendingTurnCount: 0,
2392
+ oldestBufferedTurnAgeMs: null,
2393
+ degradedReason: extraction.degraded ? "daemon extraction pipeline degraded" : null,
2394
+ }
2395
+ : extraction;
2373
2396
  // ONE call: the corpus array and the flag describing it must not come from
2374
2397
  // two independently-cached scans (round 8, codex P1).
2375
- const caps = tokenCapabilityStore.getStore();
2376
2398
  const corpusCensus = await computeServiceCorpusCensus(this.orchestrator, {
2377
2399
  cache: this.corpusWatermarkCache, caps });
2378
2400
  let projectionAvailable = false;
@@ -2398,7 +2420,7 @@ export class EngramAccessService {
2398
2420
  ),
2399
2421
  nativeKnowledgeEnabled: this.orchestrator.config.nativeKnowledge?.enabled === true,
2400
2422
  projectionAvailable,
2401
- extraction,
2423
+ extraction: visibleExtraction,
2402
2424
  corpus: corpusCensus.watermarks,
2403
2425
  // Describes THIS response's array (round 8): a separate scan could report
2404
2426
  // complete while the shipped corpus was partial, and peers trust it.
package/src/cli.ts CHANGED
@@ -31,6 +31,7 @@ import { chunkContent } from "./chunking.js";
31
31
  import { rescoreMemoryImportance } from "./importance.js";
32
32
  import { renderQmdBacklogStatus } from "./qmd-status.js";
33
33
  import { renderExtractionLivenessStats } from "./extraction-liveness.js";
34
+ import { readAggregateExtractionWatermark } from "./orchestration/extraction-watermark.js";
34
35
  import { exportJsonBundle } from "./transfer/export-json.js";
35
36
  import { exportMarkdownBundle } from "./transfer/export-md.js";
36
37
  import { backupMemoryDir } from "./transfer/backup.js";
@@ -3653,9 +3654,7 @@ export function registerCli(
3653
3654
  .command("stats")
3654
3655
  .description("Show memory system statistics")
3655
3656
  .action(async () => {
3656
- // Ensure QMD is probed before checking availability
3657
3657
  await orchestrator.qmd.probe();
3658
-
3659
3658
  const memories = await orchestrator.storage.readAllMemories();
3660
3659
  const entities = await orchestrator.storage.readEntities();
3661
3660
  const profile = await orchestrator.storage.readProfile();
@@ -3664,7 +3663,8 @@ export function registerCli(
3664
3663
  console.log(`Total memories: ${memories.length}`);
3665
3664
  console.log(`Total entities: ${entities.length}`);
3666
3665
  console.log(`Profile size: ${profile.length} chars`);
3667
- for (const line of await renderExtractionLivenessStats(orchestrator)) console.log(line);
3666
+ const extractionWatermark = await readAggregateExtractionWatermark({ config: orchestrator.config, rootStorage: orchestrator.storage, storageForNamespace: (ns) => orchestrator.getStorage(ns) });
3667
+ for (const line of await renderExtractionLivenessStats(orchestrator, extractionWatermark)) console.log(line);
3668
3668
  console.log(`QMD: ${orchestrator.qmd.isAvailable() ? "available" : "not available"}`);
3669
3669
  for (const line of await renderQmdBacklogStatus(orchestrator.qmd, orchestrator.config.qmdEmbeddingBacklogThreshold)) console.log(line);
3670
3670
 
@@ -780,6 +780,22 @@ test("finding round-7: namespace enumeration is cached (resolved once per TTL, s
780
780
  assert.equal(resolveCalls, 2, "re-enumerated once the TTL elapsed");
781
781
  });
782
782
 
783
+ test("computeServiceCorpusCensus returns incomplete when roots refresh error is set in cache", async () => {
784
+ const cache = new CorpusWatermarkCache();
785
+ const host = {
786
+ config: parseConfig({ namespacesEnabled: true }),
787
+ getStorage: async () => ({ readAllMemories: async () => [] } as unknown as StorageManager),
788
+ };
789
+ const failingResolve = async (): Promise<CorpusNamespaceRoot[]> => {
790
+ throw new Error("disk unreadable");
791
+ };
792
+ cache.getResolvedRootsStatus(failingResolve);
793
+ await cache.whenIdle();
794
+ const census = await computeServiceCorpusCensus(host, { cache });
795
+ assert.equal(census.complete, false);
796
+ assert.deepEqual(census.watermarks, []);
797
+ });
798
+
783
799
  test("finding round-9: background refresh scans are bounded to maxConcurrentRefreshes", async () => {
784
800
  const cache = new CorpusWatermarkCache({ maxConcurrentRefreshes: 2 });
785
801
  let active = 0;
@@ -312,9 +312,13 @@ export interface CorpusNamespaceRoot {
312
312
  */
313
313
  export async function resolveCorpusNamespaceRoots(options: {
314
314
  config: PluginConfig;
315
+ propagateDiscoveryErrors?: boolean;
315
316
  }): Promise<CorpusNamespaceRoot[]> {
316
317
  const { config } = options;
317
- const configDriven = await listNamespaces({ config });
318
+ const configDriven = await listNamespaces({
319
+ config,
320
+ propagateDiscoveryErrors: options.propagateDiscoveryErrors,
321
+ });
318
322
  // Default namespace first so it wins the representative label when several
319
323
  // configured names collapse onto one root (namespaces disabled → memoryDir).
320
324
  const ordered = [
@@ -376,6 +380,7 @@ export class CorpusWatermarkCache {
376
380
  private readonly clock: () => number;
377
381
  private rootsEntry: { value: CorpusNamespaceRoot[]; expiresAt: number } | undefined;
378
382
  private rootsInFlight: Promise<void> | undefined;
383
+ private rootsRefreshError: unknown;
379
384
  private readonly queued = new Map<string, () => void>();
380
385
  private activeRefreshes = 0;
381
386
  private readonly maxConcurrentRefreshes: number;
@@ -444,9 +449,12 @@ export class CorpusWatermarkCache {
444
449
  this.rootsInFlight = compute()
445
450
  .then((value) => {
446
451
  this.rootsEntry = { value, expiresAt: this.clock() + this.ttlMs };
452
+ this.rootsRefreshError = undefined;
447
453
  })
448
- .catch(() => {
449
- // Enumeration failed: keep serving any stale roots; retry next probe.
454
+ .catch((error: unknown) => {
455
+ this.rootsRefreshError = error;
456
+ // Keep serving any stale roots; callers that require completeness
457
+ // inspect the refresh status before trusting them.
450
458
  })
451
459
  .finally(() => {
452
460
  this.rootsInFlight = undefined;
@@ -454,6 +462,14 @@ export class CorpusWatermarkCache {
454
462
  }
455
463
  return this.rootsEntry?.value;
456
464
  }
465
+
466
+ getResolvedRootsStatus(compute: () => Promise<CorpusNamespaceRoot[]>): {
467
+ roots: CorpusNamespaceRoot[] | undefined;
468
+ refreshError: unknown;
469
+ } {
470
+ const roots = this.getResolvedRoots(compute);
471
+ return { roots, refreshError: this.rootsRefreshError };
472
+ }
457
473
  /** Await every background refresh — in-flight AND queued (issue #2156 round-9) —
458
474
  * plus any roots refresh (shutdown / deterministic tests). */
459
475
  async whenIdle(): Promise<void> {
@@ -518,10 +534,17 @@ export async function computeServiceCorpusCensus(
518
534
  ): Promise<{ watermarks: CorpusWatermark[]; complete: boolean }> {
519
535
  let roots: CorpusNamespaceRoot[] | undefined;
520
536
  if (options.cache) {
521
- roots = options.cache.getResolvedRoots(() => resolveCorpusNamespaceRoots({ config: host.config }));
537
+ const { roots: cachedRoots, refreshError } = options.cache.getResolvedRootsStatus(() =>
538
+ resolveCorpusNamespaceRoots({ config: host.config, propagateDiscoveryErrors: true }),
539
+ );
540
+ if (refreshError !== undefined) return { watermarks: [], complete: false };
541
+ roots = cachedRoots;
522
542
  } else {
523
543
  try {
524
- roots = await resolveCorpusNamespaceRoots({ config: host.config });
544
+ roots = await resolveCorpusNamespaceRoots({
545
+ config: host.config,
546
+ propagateDiscoveryErrors: true,
547
+ });
525
548
  } catch {
526
549
  return { watermarks: [], complete: false }; // never enumerated — not an empty corpus
527
550
  }
@@ -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");