@remnic/core 9.3.654 → 9.3.655

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 (246) hide show
  1. package/dist/access-cli.js +23 -23
  2. package/dist/access-http.d.ts +4 -4
  3. package/dist/access-http.js +16 -16
  4. package/dist/access-mcp.d.ts +4 -4
  5. package/dist/access-mcp.js +15 -15
  6. package/dist/{access-service-C8A5hoXJ.d.ts → access-service-BEJvriUt.d.ts} +2 -2
  7. package/dist/access-service.d.ts +4 -4
  8. package/dist/access-service.js +14 -14
  9. package/dist/action-confidence.d.ts +1 -1
  10. package/dist/active-memory-bridge.d.ts +1 -1
  11. package/dist/active-recall.d.ts +1 -1
  12. package/dist/active-recall.js +1 -1
  13. package/dist/behavior-learner.d.ts +1 -1
  14. package/dist/behavior-signals.d.ts +1 -1
  15. package/dist/bootstrap.d.ts +3 -3
  16. package/dist/briefing.d.ts +1 -1
  17. package/dist/briefing.js +3 -3
  18. package/dist/buffer-surprise-report.d.ts +1 -1
  19. package/dist/buffer.d.ts +1 -1
  20. package/dist/calibration.d.ts +1 -1
  21. package/dist/causal-behavior.d.ts +1 -1
  22. package/dist/causal-consolidation.d.ts +1 -1
  23. package/dist/causal-consolidation.js +4 -4
  24. package/dist/{chunk-BP2EV6W5.js → chunk-2RCGZ67B.js} +4 -4
  25. package/dist/{chunk-FVRBLJP6.js → chunk-54LOUIBE.js} +2 -2
  26. package/dist/{chunk-LJCEWTG3.js → chunk-55ZMNKMQ.js} +8 -8
  27. package/dist/{chunk-7WEB3FLJ.js → chunk-5PLUC5OB.js} +2 -2
  28. package/dist/{chunk-OL2364SB.js → chunk-5QD3QD76.js} +854 -223
  29. package/dist/chunk-5QD3QD76.js.map +1 -0
  30. package/dist/{chunk-DBM2BD22.js → chunk-67G4T7KI.js} +3 -3
  31. package/dist/{chunk-JX2RINDR.js → chunk-6G5JEN55.js} +2 -2
  32. package/dist/{chunk-2DSTAWNZ.js → chunk-A3Y37UWI.js} +3 -3
  33. package/dist/{chunk-T2C6QJG2.js → chunk-BGKXTVNG.js} +2 -2
  34. package/dist/{chunk-JYN7QNTA.js → chunk-COVZLGMR.js} +17 -17
  35. package/dist/{chunk-JVRPJ7D4.js → chunk-EKQMQQ3U.js} +48 -12
  36. package/dist/chunk-EKQMQQ3U.js.map +1 -0
  37. package/dist/{chunk-JBHXMCYN.js → chunk-GRYAECRV.js} +2 -2
  38. package/dist/{chunk-RGPUQ66K.js → chunk-IQ53ZSXV.js} +2 -2
  39. package/dist/{chunk-YLZLPVKK.js → chunk-KOI765XP.js} +104 -1
  40. package/dist/chunk-KOI765XP.js.map +1 -0
  41. package/dist/{chunk-QW6JZO5P.js → chunk-KZZ4YAEC.js} +2 -2
  42. package/dist/{chunk-2DGQLOOM.js → chunk-M3VYPE2H.js} +1 -1
  43. package/dist/{chunk-2DGQLOOM.js.map → chunk-M3VYPE2H.js.map} +1 -1
  44. package/dist/{chunk-QKK64Z6M.js → chunk-NCSJKK23.js} +2 -2
  45. package/dist/{chunk-NCGWXCSW.js → chunk-NRBGRZW4.js} +2 -2
  46. package/dist/{chunk-GPW2E4LN.js → chunk-OKW6F5S5.js} +3 -3
  47. package/dist/{chunk-Y2RIIF6H.js → chunk-PTMJ2FH2.js} +2 -2
  48. package/dist/{chunk-JMQSYGXS.js → chunk-PVE7KSQP.js} +2 -2
  49. package/dist/{chunk-NAZWHTYV.js → chunk-QDVQ4AN2.js} +4 -4
  50. package/dist/{chunk-PYWNNF2I.js → chunk-QRSKPI62.js} +99 -66
  51. package/dist/chunk-QRSKPI62.js.map +1 -0
  52. package/dist/{chunk-YM3LR4LS.js → chunk-SSSXWIBP.js} +5 -5
  53. package/dist/{chunk-NE2JBMLN.js → chunk-TDZSSJV4.js} +1 -1
  54. package/dist/chunk-TDZSSJV4.js.map +1 -0
  55. package/dist/{chunk-6CVI6BP6.js → chunk-TEO46GMM.js} +2 -2
  56. package/dist/{chunk-K6X553JB.js → chunk-UCEABZZN.js} +2 -2
  57. package/dist/{chunk-ENV6RDTD.js → chunk-UCEDY5M7.js} +2 -2
  58. package/dist/{chunk-ZCORQM74.js → chunk-UYNFWZWG.js} +2 -2
  59. package/dist/{chunk-XWQ6ERUG.js → chunk-WDTUYOLS.js} +2 -2
  60. package/dist/{chunk-Z5MQI7K2.js → chunk-XOFXKASO.js} +2 -2
  61. package/dist/{chunk-3RACUBII.js → chunk-XRKQOQLY.js} +2 -2
  62. package/dist/{chunk-BJA6DQOC.js → chunk-YYN3LIYA.js} +5 -5
  63. package/dist/{cli-uQgvDFNE.d.ts → cli-BGahB_d3.d.ts} +3 -3
  64. package/dist/cli.d.ts +5 -5
  65. package/dist/cli.js +28 -28
  66. package/dist/compounding/engine.d.ts +1 -1
  67. package/dist/compounding/engine.js +3 -3
  68. package/dist/compounding/preference-consolidator.d.ts +1 -1
  69. package/dist/compression-optimizer.d.ts +1 -1
  70. package/dist/config.d.ts +1 -1
  71. package/dist/config.js +1 -1
  72. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  73. package/dist/connectors/codex-materialize-runner.js +3 -3
  74. package/dist/connectors/codex-materialize.d.ts +1 -1
  75. package/dist/connectors/index.d.ts +1 -1
  76. package/dist/connectors/index.js +3 -3
  77. package/dist/consolidation-provenance-check.d.ts +1 -1
  78. package/dist/consolidation-undo.d.ts +1 -1
  79. package/dist/contradiction/index.d.ts +1 -1
  80. package/dist/conversation-index/backend.d.ts +1 -1
  81. package/dist/conversation-index/chunker.d.ts +1 -1
  82. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  83. package/dist/conversation-index/indexer.d.ts +1 -1
  84. package/dist/conversation-index/search.d.ts +1 -1
  85. package/dist/day-summary.d.ts +1 -1
  86. package/dist/delinearize.d.ts +1 -1
  87. package/dist/direct-answer-wiring.d.ts +1 -1
  88. package/dist/direct-answer.d.ts +1 -1
  89. package/dist/embedding-fallback.d.ts +1 -1
  90. package/dist/enrichment/index.d.ts +1 -1
  91. package/dist/entity-retrieval.d.ts +1 -1
  92. package/dist/entity-retrieval.js +3 -3
  93. package/dist/entity-schema.d.ts +1 -1
  94. package/dist/explicit-capture.d.ts +3 -3
  95. package/dist/extraction-judge-telemetry.d.ts +1 -1
  96. package/dist/extraction-judge-training.d.ts +1 -1
  97. package/dist/extraction-judge.d.ts +1 -1
  98. package/dist/extraction.d.ts +1 -1
  99. package/dist/fallback-llm.d.ts +1 -1
  100. package/dist/identity-continuity.d.ts +1 -1
  101. package/dist/importance.d.ts +1 -1
  102. package/dist/index.d.ts +8 -8
  103. package/dist/index.js +33 -33
  104. package/dist/intent.d.ts +1 -1
  105. package/dist/lcm/engine.d.ts +1 -1
  106. package/dist/lcm/index.d.ts +1 -1
  107. package/dist/lcm/tools.d.ts +1 -1
  108. package/dist/lifecycle.d.ts +1 -1
  109. package/dist/live-connectors-runner.d.ts +1 -1
  110. package/dist/local-llm.d.ts +1 -1
  111. package/dist/maintenance/memory-governance.d.ts +1 -1
  112. package/dist/maintenance/memory-governance.js +3 -3
  113. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  114. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  115. package/dist/mcp-memory-inspector-app.d.ts +4 -4
  116. package/dist/memory-action-policy.d.ts +1 -1
  117. package/dist/memory-cache.d.ts +1 -1
  118. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  119. package/dist/memory-projection-store.d.ts +1 -1
  120. package/dist/memory-provenance.d.ts +1 -1
  121. package/dist/memory-worth-outcomes.d.ts +1 -1
  122. package/dist/models-json.d.ts +1 -1
  123. package/dist/namespaces/migrate.d.ts +1 -1
  124. package/dist/namespaces/migrate.js +11 -11
  125. package/dist/namespaces/principal.d.ts +1 -1
  126. package/dist/namespaces/search.d.ts +15 -4
  127. package/dist/namespaces/search.js +7 -7
  128. package/dist/namespaces/storage.d.ts +1 -1
  129. package/dist/namespaces/storage.js +3 -3
  130. package/dist/native-knowledge.d.ts +1 -1
  131. package/dist/operator-toolkit.d.ts +1 -1
  132. package/dist/operator-toolkit.js +14 -14
  133. package/dist/{orchestrator-B4Y4sWQH.d.ts → orchestrator-BgzZlWxH.d.ts} +11 -14
  134. package/dist/orchestrator.d.ts +3 -3
  135. package/dist/orchestrator.js +19 -19
  136. package/dist/patterns-cli.d.ts +1 -1
  137. package/dist/policy-runtime.d.ts +1 -1
  138. package/dist/qmd-recall-cache.d.ts +1 -1
  139. package/dist/qmd.d.ts +5 -1
  140. package/dist/qmd.js +2 -2
  141. package/dist/recall-disclosure-escalation.d.ts +1 -1
  142. package/dist/recall-explain-renderer.d.ts +1 -1
  143. package/dist/recall-explain-renderer.js +3 -3
  144. package/dist/recall-planner-llm.d.ts +1 -1
  145. package/dist/recall-state.d.ts +1 -1
  146. package/dist/recall-tag-filter.d.ts +1 -1
  147. package/dist/recall-xray-cli.d.ts +1 -1
  148. package/dist/recall-xray-cli.js +4 -4
  149. package/dist/recall-xray-renderer.d.ts +1 -1
  150. package/dist/recall-xray-renderer.js +3 -3
  151. package/dist/recall-xray.d.ts +1 -1
  152. package/dist/recall-xray.js +2 -2
  153. package/dist/resolve-auth-token.d.ts +1 -1
  154. package/dist/resume-bundles.js +2 -2
  155. package/dist/retrieval-agents.d.ts +1 -1
  156. package/dist/retrieval-tiers.d.ts +1 -1
  157. package/dist/routing/engine.d.ts +1 -1
  158. package/dist/routing/store.d.ts +1 -1
  159. package/dist/schemas.d.ts +22 -22
  160. package/dist/search/embed-helper.d.ts +1 -1
  161. package/dist/search/factory.d.ts +1 -1
  162. package/dist/search/factory.js +6 -6
  163. package/dist/search/index.d.ts +1 -1
  164. package/dist/search/index.js +6 -6
  165. package/dist/search/lancedb-backend.d.ts +1 -1
  166. package/dist/search/lancedb-backend.js +2 -2
  167. package/dist/search/meilisearch-backend.d.ts +1 -1
  168. package/dist/search/meilisearch-backend.js +2 -2
  169. package/dist/search/noop-backend.d.ts +1 -1
  170. package/dist/search/orama-backend.d.ts +1 -1
  171. package/dist/search/orama-backend.js +2 -2
  172. package/dist/search/port.d.ts +17 -1
  173. package/dist/search/port.js +1 -1
  174. package/dist/search/remote-backend.d.ts +1 -1
  175. package/dist/{semantic-consolidation-BKd0Pype.d.ts → semantic-consolidation-Z8d_uMq8.d.ts} +1 -1
  176. package/dist/semantic-consolidation.d.ts +2 -2
  177. package/dist/semantic-consolidation.js +4 -4
  178. package/dist/semantic-rule-promotion.js +3 -3
  179. package/dist/semantic-rule-verifier.d.ts +1 -1
  180. package/dist/semantic-rule-verifier.js +3 -3
  181. package/dist/session-observer-bands.d.ts +1 -1
  182. package/dist/session-observer-state.d.ts +1 -1
  183. package/dist/shared-context/manager.d.ts +1 -1
  184. package/dist/signal.d.ts +1 -1
  185. package/dist/storage.d.ts +1 -1
  186. package/dist/storage.js +2 -2
  187. package/dist/summarizer.d.ts +1 -1
  188. package/dist/summary-snapshot.d.ts +1 -1
  189. package/dist/temporal-supersession.d.ts +1 -1
  190. package/dist/temporal-validity.d.ts +1 -1
  191. package/dist/threading.d.ts +1 -1
  192. package/dist/tier-migration.d.ts +1 -1
  193. package/dist/tier-routing.d.ts +1 -1
  194. package/dist/topics.d.ts +1 -1
  195. package/dist/transcript.d.ts +1 -1
  196. package/dist/transfer/types.d.ts +12 -12
  197. package/dist/{types-BgChEr0M.d.ts → types-2OPlQWJG.d.ts} +12 -0
  198. package/dist/types.d.ts +1 -1
  199. package/dist/types.js +1 -1
  200. package/dist/utility-runtime.d.ts +1 -1
  201. package/dist/verified-recall.js +3 -3
  202. package/package.json +1 -1
  203. package/src/config.test.ts +69 -0
  204. package/src/config.ts +135 -0
  205. package/src/maintenance/namespace-planner.test.ts +1120 -0
  206. package/src/maintenance/namespace-planner.ts +893 -0
  207. package/src/namespaces/search.test.ts +130 -2
  208. package/src/namespaces/search.ts +71 -10
  209. package/src/orchestrator-flush.test.ts +464 -44
  210. package/src/orchestrator.ts +97 -66
  211. package/src/qmd-client.test.ts +59 -0
  212. package/src/qmd.ts +124 -84
  213. package/src/search/port.ts +16 -0
  214. package/src/types.ts +12 -0
  215. package/dist/chunk-JVRPJ7D4.js.map +0 -1
  216. package/dist/chunk-NE2JBMLN.js.map +0 -1
  217. package/dist/chunk-OL2364SB.js.map +0 -1
  218. package/dist/chunk-PYWNNF2I.js.map +0 -1
  219. package/dist/chunk-YLZLPVKK.js.map +0 -1
  220. /package/dist/{chunk-BP2EV6W5.js.map → chunk-2RCGZ67B.js.map} +0 -0
  221. /package/dist/{chunk-FVRBLJP6.js.map → chunk-54LOUIBE.js.map} +0 -0
  222. /package/dist/{chunk-LJCEWTG3.js.map → chunk-55ZMNKMQ.js.map} +0 -0
  223. /package/dist/{chunk-7WEB3FLJ.js.map → chunk-5PLUC5OB.js.map} +0 -0
  224. /package/dist/{chunk-DBM2BD22.js.map → chunk-67G4T7KI.js.map} +0 -0
  225. /package/dist/{chunk-JX2RINDR.js.map → chunk-6G5JEN55.js.map} +0 -0
  226. /package/dist/{chunk-2DSTAWNZ.js.map → chunk-A3Y37UWI.js.map} +0 -0
  227. /package/dist/{chunk-T2C6QJG2.js.map → chunk-BGKXTVNG.js.map} +0 -0
  228. /package/dist/{chunk-JYN7QNTA.js.map → chunk-COVZLGMR.js.map} +0 -0
  229. /package/dist/{chunk-JBHXMCYN.js.map → chunk-GRYAECRV.js.map} +0 -0
  230. /package/dist/{chunk-RGPUQ66K.js.map → chunk-IQ53ZSXV.js.map} +0 -0
  231. /package/dist/{chunk-QW6JZO5P.js.map → chunk-KZZ4YAEC.js.map} +0 -0
  232. /package/dist/{chunk-QKK64Z6M.js.map → chunk-NCSJKK23.js.map} +0 -0
  233. /package/dist/{chunk-NCGWXCSW.js.map → chunk-NRBGRZW4.js.map} +0 -0
  234. /package/dist/{chunk-GPW2E4LN.js.map → chunk-OKW6F5S5.js.map} +0 -0
  235. /package/dist/{chunk-Y2RIIF6H.js.map → chunk-PTMJ2FH2.js.map} +0 -0
  236. /package/dist/{chunk-JMQSYGXS.js.map → chunk-PVE7KSQP.js.map} +0 -0
  237. /package/dist/{chunk-NAZWHTYV.js.map → chunk-QDVQ4AN2.js.map} +0 -0
  238. /package/dist/{chunk-YM3LR4LS.js.map → chunk-SSSXWIBP.js.map} +0 -0
  239. /package/dist/{chunk-6CVI6BP6.js.map → chunk-TEO46GMM.js.map} +0 -0
  240. /package/dist/{chunk-K6X553JB.js.map → chunk-UCEABZZN.js.map} +0 -0
  241. /package/dist/{chunk-ENV6RDTD.js.map → chunk-UCEDY5M7.js.map} +0 -0
  242. /package/dist/{chunk-ZCORQM74.js.map → chunk-UYNFWZWG.js.map} +0 -0
  243. /package/dist/{chunk-XWQ6ERUG.js.map → chunk-WDTUYOLS.js.map} +0 -0
  244. /package/dist/{chunk-Z5MQI7K2.js.map → chunk-XOFXKASO.js.map} +0 -0
  245. /package/dist/{chunk-3RACUBII.js.map → chunk-XRKQOQLY.js.map} +0 -0
  246. /package/dist/{chunk-BJA6DQOC.js.map → chunk-YYN3LIYA.js.map} +0 -0
@@ -11,6 +11,7 @@ import { parseConfig } from "./config.js";
11
11
  import type { BufferTurn } from "./types.js";
12
12
  import type { ImportTurn } from "./bulk-import/types.js";
13
13
  import { namespaceIdentityToken } from "./namespaces/identity.js";
14
+ import { readNamespaceMaintenanceStatuses } from "./maintenance/namespace-planner.js";
14
15
 
15
16
  function makeTurn(sessionKey: string, content: string): BufferTurn {
16
17
  return {
@@ -1780,8 +1781,10 @@ test("runExtraction still clears the session buffer after persistence even if re
1780
1781
  // cataloged namespaces.
1781
1782
  test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)", async () => {
1782
1783
  const orchestrator = Object.create(Orchestrator.prototype) as any;
1783
- let updateArg: string[] | undefined;
1784
- let embedArg: string[] | undefined;
1784
+ const updateArgs: string[] = [];
1785
+ const updateCalls: Array<{ namespaces: string[]; strict: boolean | undefined }> = [];
1786
+ const embedArgs: string[] = [];
1787
+ const embedCalls: string[][] = [];
1785
1788
  const memoryDir = path.join(os.tmpdir(), "remnic-qmd-maintenance-ngnei");
1786
1789
  const dynamicNamespace = "project-origin-dynamic";
1787
1790
  const dynamicStorageDir = path.join(
@@ -1797,6 +1800,7 @@ test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)"
1797
1800
  defaultNamespace: "default",
1798
1801
  sharedNamespace: "shared",
1799
1802
  namespacePolicies: [],
1803
+ maintenanceNamespaceLockStaleMs: 100,
1800
1804
  qmdAutoEmbedEnabled: true,
1801
1805
  qmdEmbedMinIntervalMs: 0,
1802
1806
  };
@@ -1820,35 +1824,98 @@ test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)"
1820
1824
  },
1821
1825
  };
1822
1826
  orchestrator.namespaceSearchRouter = {
1823
- async updateNamespaces(ns: string[]) {
1824
- updateArg = ns;
1825
- return ns.length;
1827
+ async updateNamespacesDetailed(ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
1828
+ updateCalls.push({ namespaces: [...ns], strict: options?.strict });
1829
+ updateArgs.push(...ns);
1830
+ return { backendCount: ns.length, eligibleNamespaces: ns };
1826
1831
  },
1827
1832
  async embedNamespaces(ns: string[]) {
1828
- embedArg = ns;
1833
+ embedCalls.push([...ns]);
1834
+ embedArgs.push(...ns);
1829
1835
  },
1830
1836
  };
1831
1837
 
1832
1838
  await orchestrator.runQmdMaintenance();
1833
1839
 
1834
- assert.ok(updateArg, "updateNamespaces must be called");
1840
+ assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
1841
+ assert.equal(updateCalls.length, 1, "global QMD maintenance must batch selected namespaces into one update call");
1842
+ assert.equal(updateCalls[0]?.strict, true, "recurring QMD maintenance must use strict update semantics");
1835
1843
  assert.ok(
1836
- updateArg!.includes(dynamicNamespace),
1844
+ updateArgs.includes(dynamicNamespace),
1837
1845
  "QMD update must cover the cataloged dynamic namespace, not just configured ones",
1838
1846
  );
1839
1847
  assert.ok(
1840
- updateArg!.includes("default") && updateArg!.includes("shared"),
1848
+ updateArgs.includes("default") && updateArgs.includes("shared"),
1841
1849
  "configured namespaces remain covered",
1842
1850
  );
1843
1851
  assert.ok(
1844
- embedArg && embedArg.includes(dynamicNamespace),
1852
+ embedArgs.includes(dynamicNamespace),
1845
1853
  "QMD embed must cover the cataloged dynamic namespace",
1846
1854
  );
1855
+ assert.equal(embedCalls.length, 1, "QMD embed must batch all selected namespaces into one router call");
1856
+ assert.deepEqual(new Set(embedCalls[0]), new Set(["default", "shared", dynamicNamespace]));
1857
+ });
1858
+
1859
+ test("runQmdMaintenance tracks namespace embed cadence across budget rotation", async () => {
1860
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
1861
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-namespace-embed-cadence-"));
1862
+ const updateCalls: string[][] = [];
1863
+ const embedCalls: string[][] = [];
1864
+
1865
+ try {
1866
+ orchestrator.config = {
1867
+ memoryDir,
1868
+ namespacesEnabled: true,
1869
+ defaultNamespace: "default",
1870
+ sharedNamespace: "shared",
1871
+ namespacePolicies: [{ name: "project-a" }, { name: "project-b" }],
1872
+ maintenanceMaxNamespacesPerCycle: 3,
1873
+ maintenanceNamespaceLockStaleMs: 100,
1874
+ qmdAutoEmbedEnabled: true,
1875
+ qmdEmbedMinIntervalMs: 60_000,
1876
+ };
1877
+ orchestrator.qmdMaintenanceInFlight = false;
1878
+ orchestrator.qmdMaintenancePending = true;
1879
+ orchestrator.lastQmdEmbedAtMs = 0;
1880
+ orchestrator.lastQmdEmbedAtMsByNamespace = new Map();
1881
+ orchestrator.namespaceCatalog = {
1882
+ enabled: false,
1883
+ async listNamespaces() {
1884
+ throw new Error("catalog disabled - must not be read");
1885
+ },
1886
+ };
1887
+ orchestrator.namespaceSearchRouter = {
1888
+ async updateNamespacesDetailed(ns: string[]) {
1889
+ updateCalls.push([...ns]);
1890
+ return { backendCount: ns.length, eligibleNamespaces: ns };
1891
+ },
1892
+ async embedNamespaces(ns: string[]) {
1893
+ embedCalls.push([...ns]);
1894
+ },
1895
+ };
1896
+
1897
+ await orchestrator.runQmdMaintenance();
1898
+ orchestrator.qmdMaintenancePending = true;
1899
+ await orchestrator.runQmdMaintenance();
1900
+
1901
+ assert.deepEqual(updateCalls, [
1902
+ ["default", "shared", "project-a"],
1903
+ ["default", "shared", "project-b"],
1904
+ ]);
1905
+ assert.deepEqual(
1906
+ embedCalls,
1907
+ [["default", "shared", "project-a"], ["project-b"]],
1908
+ "a global embed timestamp must not suppress embeddings for newly budgeted namespaces",
1909
+ );
1910
+ } finally {
1911
+ await rm(memoryDir, { recursive: true, force: true });
1912
+ }
1847
1913
  });
1848
1914
 
1849
1915
  test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is unsafe", async () => {
1850
1916
  const orchestrator = Object.create(Orchestrator.prototype) as any;
1851
- let updateArg: string[] | undefined;
1917
+ const updateArgs: string[] = [];
1918
+ const updateCalls: string[][] = [];
1852
1919
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-unsafe-root-"));
1853
1920
  const outsideDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-unsafe-target-"));
1854
1921
  try {
@@ -1868,6 +1935,7 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
1868
1935
  defaultNamespace: "default",
1869
1936
  sharedNamespace: "shared",
1870
1937
  namespacePolicies: [],
1938
+ maintenanceNamespaceLockStaleMs: 100,
1871
1939
  qmdAutoEmbedEnabled: false,
1872
1940
  qmdEmbedMinIntervalMs: 0,
1873
1941
  };
@@ -1890,18 +1958,20 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
1890
1958
  },
1891
1959
  };
1892
1960
  orchestrator.namespaceSearchRouter = {
1893
- async updateNamespaces(ns: string[]) {
1894
- updateArg = ns;
1895
- return ns.length;
1961
+ async updateNamespacesDetailed(ns: string[]) {
1962
+ updateCalls.push([...ns]);
1963
+ updateArgs.push(...ns);
1964
+ return { backendCount: ns.length, eligibleNamespaces: ns };
1896
1965
  },
1897
1966
  async embedNamespaces() {},
1898
1967
  };
1899
1968
 
1900
1969
  await orchestrator.runQmdMaintenance();
1901
1970
 
1902
- assert.ok(updateArg, "updateNamespaces must be called");
1971
+ assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
1972
+ assert.equal(updateCalls.length, 1, "global QMD maintenance must update once for the locked namespace set");
1903
1973
  assert.deepEqual(
1904
- [...updateArg!].sort(),
1974
+ [...updateArgs].sort(),
1905
1975
  ["default", "shared"],
1906
1976
  "cataloged dynamic namespaces are skipped when the live router root differs from the catalog-sanitized root",
1907
1977
  );
@@ -1911,46 +1981,395 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
1911
1981
  }
1912
1982
  });
1913
1983
 
1984
+ test("runQmdMaintenance treats zero namespace updates as failed maintenance", async () => {
1985
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
1986
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-zero-update-"));
1987
+ let markMaintenanceCalls = 0;
1988
+ let embedCalls = 0;
1989
+ try {
1990
+ orchestrator.config = {
1991
+ memoryDir,
1992
+ namespacesEnabled: true,
1993
+ defaultNamespace: "default",
1994
+ sharedNamespace: "shared",
1995
+ namespacePolicies: [],
1996
+ maintenanceNamespaceLockStaleMs: 100,
1997
+ qmdAutoEmbedEnabled: false,
1998
+ qmdEmbedMinIntervalMs: 0,
1999
+ };
2000
+ orchestrator.qmdMaintenanceInFlight = false;
2001
+ orchestrator.qmdMaintenancePending = true;
2002
+ orchestrator.lastQmdEmbedAtMs = 0;
2003
+ orchestrator.namespaceCatalog = {
2004
+ enabled: true,
2005
+ async listNamespaces() {
2006
+ return [{ namespace: "default" }];
2007
+ },
2008
+ async markMaintenance() {
2009
+ markMaintenanceCalls += 1;
2010
+ },
2011
+ };
2012
+ orchestrator.namespaceSearchRouter = {
2013
+ async updateNamespacesDetailed() {
2014
+ return { backendCount: 0, eligibleNamespaces: [] };
2015
+ },
2016
+ async embedNamespaces() {
2017
+ embedCalls += 1;
2018
+ },
2019
+ };
2020
+
2021
+ await orchestrator.runQmdMaintenance();
2022
+
2023
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2024
+ assert.ok(
2025
+ statuses.some((status) => status.namespace === "default" && status.state === "failed"),
2026
+ "zero updates should be recorded as failed maintenance, not a successful run",
2027
+ );
2028
+ assert.equal(markMaintenanceCalls, 0);
2029
+ assert.equal(embedCalls, 0);
2030
+ assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
2031
+ } finally {
2032
+ await rm(memoryDir, { recursive: true, force: true });
2033
+ }
2034
+ });
2035
+
2036
+ test("runQmdMaintenance treats partial namespace update eligibility as failed maintenance", async () => {
2037
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
2038
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-partial-update-"));
2039
+ let markMaintenanceCalls = 0;
2040
+ let embedCalls = 0;
2041
+ try {
2042
+ orchestrator.config = {
2043
+ memoryDir,
2044
+ namespacesEnabled: true,
2045
+ defaultNamespace: "default",
2046
+ sharedNamespace: "shared",
2047
+ namespacePolicies: [],
2048
+ maintenanceNamespaceLockStaleMs: 100,
2049
+ qmdAutoEmbedEnabled: false,
2050
+ qmdEmbedMinIntervalMs: 0,
2051
+ };
2052
+ orchestrator.qmdMaintenanceInFlight = false;
2053
+ orchestrator.qmdMaintenancePending = true;
2054
+ orchestrator.lastQmdEmbedAtMs = 0;
2055
+ orchestrator.namespaceCatalog = {
2056
+ enabled: true,
2057
+ async listNamespaces() {
2058
+ return [{ namespace: "default" }];
2059
+ },
2060
+ async markMaintenance() {
2061
+ markMaintenanceCalls += 1;
2062
+ },
2063
+ };
2064
+ orchestrator.namespaceSearchRouter = {
2065
+ async updateNamespacesDetailed(ns: string[]) {
2066
+ assert.ok(ns.includes("default") && ns.includes("shared"));
2067
+ return { backendCount: 1, eligibleNamespaces: ["default"] };
2068
+ },
2069
+ async embedNamespaces() {
2070
+ embedCalls += 1;
2071
+ },
2072
+ };
2073
+
2074
+ await orchestrator.runQmdMaintenance();
2075
+
2076
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2077
+ assert.ok(
2078
+ statuses.some((status) => status.namespace === "default" && status.state === "failed"),
2079
+ "partial update eligibility should not be recorded as successful maintenance",
2080
+ );
2081
+ assert.ok(
2082
+ statuses.some((status) => status.namespace === "shared" && status.state === "failed"),
2083
+ "ineligible selected namespaces should not be rotated as maintained",
2084
+ );
2085
+ assert.equal(markMaintenanceCalls, 0);
2086
+ assert.equal(embedCalls, 0);
2087
+ assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
2088
+ } finally {
2089
+ await rm(memoryDir, { recursive: true, force: true });
2090
+ }
2091
+ });
2092
+
2093
+ test("runQmdMaintenance treats namespace embed errors as failed maintenance", async () => {
2094
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
2095
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-embed-failure-"));
2096
+ let markMaintenanceCalls = 0;
2097
+ try {
2098
+ orchestrator.config = {
2099
+ memoryDir,
2100
+ namespacesEnabled: true,
2101
+ defaultNamespace: "default",
2102
+ sharedNamespace: "shared",
2103
+ namespacePolicies: [],
2104
+ maintenanceNamespaceLockStaleMs: 100,
2105
+ qmdAutoEmbedEnabled: true,
2106
+ qmdEmbedMinIntervalMs: 0,
2107
+ };
2108
+ orchestrator.qmdMaintenanceInFlight = false;
2109
+ orchestrator.qmdMaintenancePending = true;
2110
+ orchestrator.lastQmdEmbedAtMs = 0;
2111
+ orchestrator.namespaceCatalog = {
2112
+ enabled: true,
2113
+ async listNamespaces() {
2114
+ return [{ namespace: "default" }];
2115
+ },
2116
+ async markMaintenance() {
2117
+ markMaintenanceCalls += 1;
2118
+ },
2119
+ };
2120
+ orchestrator.namespaceSearchRouter = {
2121
+ async updateNamespacesDetailed(ns: string[]) {
2122
+ return { backendCount: 1, eligibleNamespaces: ns };
2123
+ },
2124
+ async embedNamespaces() {
2125
+ throw Object.assign(new Error("embed failed"), { code: "EQMD" });
2126
+ },
2127
+ };
2128
+
2129
+ await orchestrator.runQmdMaintenance();
2130
+
2131
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2132
+ assert.ok(
2133
+ statuses.some((status) => status.namespace === "default" && status.state === "failed"),
2134
+ "embed failures should not be recorded as successful namespace maintenance",
2135
+ );
2136
+ assert.equal(markMaintenanceCalls, 0);
2137
+ assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
2138
+ } finally {
2139
+ await rm(memoryDir, { recursive: true, force: true });
2140
+ }
2141
+ });
2142
+
2143
+ test("runQmdMaintenance records QMD min-interval throttles as skipped maintenance", async () => {
2144
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
2145
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-throttled-update-"));
2146
+ let markMaintenanceCalls = 0;
2147
+ let embedCalls = 0;
2148
+ let updateCalls = 0;
2149
+ try {
2150
+ orchestrator.config = {
2151
+ memoryDir,
2152
+ namespacesEnabled: true,
2153
+ defaultNamespace: "default",
2154
+ sharedNamespace: "shared",
2155
+ namespacePolicies: [],
2156
+ maintenanceNamespaceLockStaleMs: 100,
2157
+ qmdAutoEmbedEnabled: false,
2158
+ qmdEmbedMinIntervalMs: 0,
2159
+ };
2160
+ orchestrator.qmdMaintenanceInFlight = false;
2161
+ orchestrator.qmdMaintenancePending = true;
2162
+ orchestrator.lastQmdEmbedAtMs = 0;
2163
+ orchestrator.namespaceCatalog = {
2164
+ enabled: true,
2165
+ async listNamespaces() {
2166
+ return [{ namespace: "default" }];
2167
+ },
2168
+ async markMaintenance() {
2169
+ markMaintenanceCalls += 1;
2170
+ },
2171
+ };
2172
+ orchestrator.namespaceSearchRouter = {
2173
+ async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
2174
+ updateCalls += 1;
2175
+ assert.equal(options?.strict, true, "recurring maintenance must use strict QMD updates");
2176
+ throw new Error("QMD update skipped by global min-interval gate");
2177
+ },
2178
+ async embedNamespaces() {
2179
+ embedCalls += 1;
2180
+ },
2181
+ };
2182
+
2183
+ await orchestrator.runQmdMaintenance();
2184
+
2185
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2186
+ assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
2187
+ assert.ok(
2188
+ statuses.some(
2189
+ (status) =>
2190
+ status.namespace === "default" &&
2191
+ status.state === "skipped" &&
2192
+ status.reason === "throttled",
2193
+ ),
2194
+ "QMD min-interval throttles should be recorded as skipped maintenance",
2195
+ );
2196
+ assert.ok(
2197
+ statuses.some(
2198
+ (status) =>
2199
+ status.namespace === "shared" &&
2200
+ status.state === "skipped" &&
2201
+ status.reason === "throttled",
2202
+ ),
2203
+ "every selected namespace should receive the throttled skip status",
2204
+ );
2205
+ assert.equal(markMaintenanceCalls, 0);
2206
+ assert.equal(embedCalls, 0);
2207
+ assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
2208
+ } finally {
2209
+ await rm(memoryDir, { recursive: true, force: true });
2210
+ }
2211
+ });
2212
+
2213
+ test("runQmdMaintenance still embeds when due update is throttled", async () => {
2214
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
2215
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-throttled-update-embed-"));
2216
+ let markMaintenanceCalls = 0;
2217
+ let updateCalls = 0;
2218
+ const embedCalls: string[][] = [];
2219
+ try {
2220
+ orchestrator.config = {
2221
+ memoryDir,
2222
+ namespacesEnabled: true,
2223
+ defaultNamespace: "default",
2224
+ sharedNamespace: "shared",
2225
+ namespacePolicies: [],
2226
+ maintenanceNamespaceLockStaleMs: 100,
2227
+ qmdAutoEmbedEnabled: true,
2228
+ qmdEmbedMinIntervalMs: 0,
2229
+ };
2230
+ orchestrator.qmdMaintenanceInFlight = false;
2231
+ orchestrator.qmdMaintenancePending = true;
2232
+ orchestrator.lastQmdEmbedAtMs = 0;
2233
+ orchestrator.namespaceCatalog = {
2234
+ enabled: true,
2235
+ async listNamespaces() {
2236
+ return [{ namespace: "default" }];
2237
+ },
2238
+ async markMaintenance() {
2239
+ markMaintenanceCalls += 1;
2240
+ },
2241
+ };
2242
+ orchestrator.namespaceSearchRouter = {
2243
+ async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
2244
+ updateCalls += 1;
2245
+ assert.equal(options?.strict, true, "recurring maintenance must use strict QMD updates");
2246
+ throw new Error("QMD update skipped by global min-interval gate");
2247
+ },
2248
+ async embedNamespaces(ns: string[], options?: { strict?: boolean }) {
2249
+ assert.equal(options?.strict, true, "due embed retries must surface embed failures");
2250
+ embedCalls.push([...ns]);
2251
+ },
2252
+ };
2253
+
2254
+ await orchestrator.runQmdMaintenance();
2255
+
2256
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2257
+ assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
2258
+ assert.deepEqual(embedCalls, [["default", "shared"]]);
2259
+ assert.ok(
2260
+ statuses.every((status) => status.state === "skipped" && status.reason === "throttled"),
2261
+ "a throttled update should still be recorded as skipped after the due embed retry",
2262
+ );
2263
+ assert.equal(markMaintenanceCalls, 0);
2264
+ assert.notEqual(orchestrator.lastQmdEmbedAtMs, 0);
2265
+ } finally {
2266
+ await rm(memoryDir, { recursive: true, force: true });
2267
+ }
2268
+ });
2269
+
2270
+ test("runQmdMaintenance treats strict namespace update errors as failed maintenance", async () => {
2271
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
2272
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-update-failure-"));
2273
+ let markMaintenanceCalls = 0;
2274
+ let embedCalls = 0;
2275
+ let updateCalls = 0;
2276
+ try {
2277
+ orchestrator.config = {
2278
+ memoryDir,
2279
+ namespacesEnabled: true,
2280
+ defaultNamespace: "default",
2281
+ sharedNamespace: "shared",
2282
+ namespacePolicies: [],
2283
+ maintenanceNamespaceLockStaleMs: 100,
2284
+ qmdAutoEmbedEnabled: true,
2285
+ qmdEmbedMinIntervalMs: 0,
2286
+ };
2287
+ orchestrator.qmdMaintenanceInFlight = false;
2288
+ orchestrator.qmdMaintenancePending = true;
2289
+ orchestrator.lastQmdEmbedAtMs = 0;
2290
+ orchestrator.namespaceCatalog = {
2291
+ enabled: true,
2292
+ async listNamespaces() {
2293
+ return [{ namespace: "default" }];
2294
+ },
2295
+ async markMaintenance() {
2296
+ markMaintenanceCalls += 1;
2297
+ },
2298
+ };
2299
+ orchestrator.namespaceSearchRouter = {
2300
+ async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
2301
+ updateCalls += 1;
2302
+ assert.equal(options?.strict, true, "recurring maintenance must require strict update failure propagation");
2303
+ throw new Error("qmd exploded");
2304
+ },
2305
+ async embedNamespaces() {
2306
+ embedCalls += 1;
2307
+ },
2308
+ };
2309
+
2310
+ await orchestrator.runQmdMaintenance();
2311
+
2312
+ const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
2313
+ assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
2314
+ assert.ok(
2315
+ statuses.some((status) => status.namespace === "default" && status.state === "failed"),
2316
+ "strict update errors should be recorded as failed maintenance",
2317
+ );
2318
+ assert.equal(markMaintenanceCalls, 0);
2319
+ assert.equal(embedCalls, 0);
2320
+ assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
2321
+ } finally {
2322
+ await rm(memoryDir, { recursive: true, force: true });
2323
+ }
2324
+ });
2325
+
1914
2326
  // NGnei fallback: when the catalog is disabled, maintenance covers exactly the
1915
2327
  // configured set (no catalog read), and a catalog read failure degrades to the
1916
2328
  // configured set rather than breaking maintenance.
1917
2329
  test("runQmdMaintenance falls back to configured namespaces when the catalog is disabled (NGnei)", async () => {
1918
2330
  const orchestrator = Object.create(Orchestrator.prototype) as any;
1919
- let updateArg: string[] | undefined;
2331
+ const updateArgs: string[] = [];
2332
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-disabled-catalog-"));
1920
2333
 
1921
- orchestrator.config = {
1922
- namespacesEnabled: true,
1923
- defaultNamespace: "default",
1924
- sharedNamespace: "shared",
2334
+ try {
2335
+ orchestrator.config = {
2336
+ memoryDir,
2337
+ namespacesEnabled: true,
2338
+ defaultNamespace: "default",
2339
+ sharedNamespace: "shared",
1925
2340
  namespacePolicies: [],
2341
+ maintenanceNamespaceLockStaleMs: 100,
1926
2342
  qmdAutoEmbedEnabled: false,
1927
- qmdEmbedMinIntervalMs: 0,
1928
- };
1929
- orchestrator.qmdMaintenanceInFlight = false;
1930
- orchestrator.qmdMaintenancePending = true;
1931
- orchestrator.lastQmdEmbedAtMs = 0;
1932
- orchestrator.namespaceCatalog = {
1933
- enabled: false,
1934
- async listNamespaces() {
1935
- throw new Error("catalog disabled — must not be read");
1936
- },
2343
+ qmdEmbedMinIntervalMs: 0,
2344
+ };
2345
+ orchestrator.qmdMaintenanceInFlight = false;
2346
+ orchestrator.qmdMaintenancePending = true;
2347
+ orchestrator.lastQmdEmbedAtMs = 0;
2348
+ orchestrator.namespaceCatalog = {
2349
+ enabled: false,
2350
+ async listNamespaces() {
2351
+ throw new Error("catalog disabled — must not be read");
2352
+ },
1937
2353
  };
1938
2354
  orchestrator.namespaceSearchRouter = {
1939
- async updateNamespaces(ns: string[]) {
1940
- updateArg = ns;
1941
- return ns.length;
2355
+ async updateNamespacesDetailed(ns: string[]) {
2356
+ updateArgs.push(...ns);
2357
+ return { backendCount: ns.length, eligibleNamespaces: ns };
1942
2358
  },
1943
- async embedNamespaces() {},
1944
- };
2359
+ async embedNamespaces() {},
2360
+ };
1945
2361
 
1946
- await orchestrator.runQmdMaintenance();
2362
+ await orchestrator.runQmdMaintenance();
1947
2363
 
1948
- assert.ok(updateArg, "updateNamespaces must be called");
1949
- assert.deepEqual(
1950
- [...updateArg!].sort(),
1951
- ["default", "shared"],
1952
- "a disabled catalog covers exactly the configured set",
1953
- );
2364
+ assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
2365
+ assert.deepEqual(
2366
+ [...updateArgs].sort(),
2367
+ ["default", "shared"],
2368
+ "a disabled catalog covers exactly the configured set",
2369
+ );
2370
+ } finally {
2371
+ await rm(memoryDir, { recursive: true, force: true });
2372
+ }
1954
2373
  });
1955
2374
 
1956
2375
  // ── NHZEV (codex P2): the QMD STARTUP sync in deferredInitialize() must cover
@@ -1981,6 +2400,7 @@ test("deferredInitialize startup sync covers cataloged dynamic namespaces (NHZEV
1981
2400
  sharedNamespace: "shared",
1982
2401
  namespacePolicies: [],
1983
2402
  qmdMaintenanceEnabled: true,
2403
+ maintenanceMaxNamespacesPerCycle: 2,
1984
2404
  };
1985
2405
  orchestrator.qmd = {
1986
2406
  isAvailable: () => true,
@@ -2017,7 +2437,7 @@ test("deferredInitialize startup sync covers cataloged dynamic namespaces (NHZEV
2017
2437
  assert.ok(updateArg, "startup updateNamespaces must be called");
2018
2438
  assert.ok(
2019
2439
  updateArg!.includes(dynamicNamespace),
2020
- "startup sync must cover the cataloged dynamic namespace (NHZEV), not just configured ones",
2440
+ "startup sync must cover the cataloged dynamic namespace even when it exceeds the recurring maintenance cycle budget",
2021
2441
  );
2022
2442
  assert.ok(
2023
2443
  updateArg!.includes("default") && updateArg!.includes("shared"),