@remnic/core 9.3.633 → 9.3.635

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 (61) hide show
  1. package/dist/access-cli.js +24 -24
  2. package/dist/access-http.d.ts +1 -1
  3. package/dist/access-http.js +22 -3
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +21 -2
  6. package/dist/{access-service-C9_EpVHd.d.ts → access-service-GVOE5U3C.d.ts} +3 -0
  7. package/dist/access-service.d.ts +1 -1
  8. package/dist/access-service.js +20 -1
  9. package/dist/{chunk-2ROPI5OE.js → chunk-6GC5SGFE.js} +4 -4
  10. package/dist/{chunk-6GDHLVJC.js → chunk-AV5ZEH4M.js} +4 -4
  11. package/dist/{chunk-32U3N7H5.js → chunk-BK35NOJL.js} +3 -3
  12. package/dist/{chunk-TZVQQTG4.js → chunk-DEDTFRH2.js} +6 -6
  13. package/dist/{chunk-5PT5I6JQ.js → chunk-JYFO32V4.js} +7 -7
  14. package/dist/{chunk-ZJR7VG5L.js → chunk-LASA46TQ.js} +2 -2
  15. package/dist/{chunk-XG4NAWAV.js → chunk-NFNQ7TWF.js} +3 -3
  16. package/dist/{chunk-XG4NAWAV.js.map → chunk-NFNQ7TWF.js.map} +1 -1
  17. package/dist/{chunk-YRMKDTKF.js → chunk-RAGXUVQW.js} +3 -3
  18. package/dist/{chunk-EWLQPEO6.js → chunk-RN7MUWON.js} +4 -4
  19. package/dist/{chunk-WR64DQFE.js → chunk-SQZ42MKH.js} +4 -4
  20. package/dist/{chunk-WLZBVYC6.js → chunk-TFICNXXU.js} +28 -28
  21. package/dist/{chunk-IFVFQRZ2.js → chunk-VTH5SKQW.js} +2 -2
  22. package/dist/{chunk-DOBJH4I6.js → chunk-XM6M6DHG.js} +78 -10
  23. package/dist/chunk-XM6M6DHG.js.map +1 -0
  24. package/dist/{chunk-VTR3MNYF.js → chunk-YCX45GOQ.js} +4 -4
  25. package/dist/{cli-CuVEQWKr.d.ts → cli-B-fHk59D.d.ts} +1 -1
  26. package/dist/cli.d.ts +2 -2
  27. package/dist/cli.js +26 -26
  28. package/dist/direct-answer-wiring.js +2 -2
  29. package/dist/harmonic-retrieval.js +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +41 -41
  32. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  33. package/dist/namespaces/migrate.js +8 -8
  34. package/dist/namespaces/search.js +6 -6
  35. package/dist/operator-toolkit.js +19 -19
  36. package/dist/orchestrator.js +23 -23
  37. package/dist/qmd.js +2 -2
  38. package/dist/retrieval-agents.js +2 -2
  39. package/dist/search/embed-helper.js +2 -2
  40. package/dist/search/factory.js +5 -5
  41. package/dist/search/index.js +5 -5
  42. package/dist/search/lancedb-backend.js +1 -1
  43. package/dist/search/meilisearch-backend.js +1 -1
  44. package/dist/search/orama-backend.js +1 -1
  45. package/package.json +1 -1
  46. package/src/access-mcp.ts +1 -1
  47. package/src/access-service-namespace.test.ts +312 -0
  48. package/src/access-service.ts +97 -11
  49. package/dist/chunk-DOBJH4I6.js.map +0 -1
  50. /package/dist/{chunk-2ROPI5OE.js.map → chunk-6GC5SGFE.js.map} +0 -0
  51. /package/dist/{chunk-6GDHLVJC.js.map → chunk-AV5ZEH4M.js.map} +0 -0
  52. /package/dist/{chunk-32U3N7H5.js.map → chunk-BK35NOJL.js.map} +0 -0
  53. /package/dist/{chunk-TZVQQTG4.js.map → chunk-DEDTFRH2.js.map} +0 -0
  54. /package/dist/{chunk-5PT5I6JQ.js.map → chunk-JYFO32V4.js.map} +0 -0
  55. /package/dist/{chunk-ZJR7VG5L.js.map → chunk-LASA46TQ.js.map} +0 -0
  56. /package/dist/{chunk-YRMKDTKF.js.map → chunk-RAGXUVQW.js.map} +0 -0
  57. /package/dist/{chunk-EWLQPEO6.js.map → chunk-RN7MUWON.js.map} +0 -0
  58. /package/dist/{chunk-WR64DQFE.js.map → chunk-SQZ42MKH.js.map} +0 -0
  59. /package/dist/{chunk-WLZBVYC6.js.map → chunk-TFICNXXU.js.map} +0 -0
  60. /package/dist/{chunk-IFVFQRZ2.js.map → chunk-VTH5SKQW.js.map} +0 -0
  61. /package/dist/{chunk-VTR3MNYF.js.map → chunk-YCX45GOQ.js.map} +0 -0
@@ -27,7 +27,7 @@ import {
27
27
  sanitizeSessionKeyForFilename,
28
28
  shouldFilterLifecycleRecallCandidate,
29
29
  summarizeGraphShadowComparison
30
- } from "./chunk-WLZBVYC6.js";
30
+ } from "./chunk-TFICNXXU.js";
31
31
  import "./chunk-KVDUDYEN.js";
32
32
  import "./chunk-5RIRL3XL.js";
33
33
  import "./chunk-4HFJQCJZ.js";
@@ -57,7 +57,7 @@ import "./chunk-HZVIYZYN.js";
57
57
  import "./chunk-XJNBEDFE.js";
58
58
  import "./chunk-MHQC2WU2.js";
59
59
  import "./chunk-OZKZ2TRP.js";
60
- import "./chunk-2ROPI5OE.js";
60
+ import "./chunk-6GC5SGFE.js";
61
61
  import "./chunk-MGN7VHWQ.js";
62
62
  import "./chunk-5IZL4DCV.js";
63
63
  import "./chunk-X7XN6YU4.js";
@@ -90,7 +90,7 @@ import "./chunk-OAZ5MFUB.js";
90
90
  import "./chunk-PYPOFEMK.js";
91
91
  import "./chunk-WLEB7WCG.js";
92
92
  import "./chunk-VEWZZM3H.js";
93
- import "./chunk-WR64DQFE.js";
93
+ import "./chunk-SQZ42MKH.js";
94
94
  import "./chunk-6HEM6HTQ.js";
95
95
  import "./chunk-XCAZF7KQ.js";
96
96
  import "./chunk-3PG3H5TD.js";
@@ -110,35 +110,17 @@ import "./chunk-ZRWB5D4H.js";
110
110
  import "./chunk-A6D7A2FW.js";
111
111
  import "./chunk-FF4KLI5W.js";
112
112
  import "./chunk-SFQ6QNL7.js";
113
- import "./chunk-ZJR7VG5L.js";
114
- import "./chunk-YRMKDTKF.js";
115
- import "./chunk-JOASJWQR.js";
116
- import "./chunk-EWLQPEO6.js";
117
- import "./chunk-3MAONBX3.js";
118
- import "./chunk-RG3LBSGH.js";
119
- import "./chunk-CYEPCZN5.js";
120
- import "./chunk-DCGT4FPP.js";
121
- import "./chunk-Q4CAQGKQ.js";
122
- import "./chunk-CINZGPSJ.js";
123
113
  import "./chunk-LEG7XWS2.js";
124
- import "./chunk-ZFXCQPNO.js";
125
- import "./chunk-Q6YIJGXJ.js";
126
- import "./chunk-ORFGK3XI.js";
127
- import "./chunk-OIF36KGD.js";
128
- import "./chunk-7DTASS5T.js";
129
114
  import "./chunk-KFY3SGN7.js";
130
- import "./chunk-TZVQQTG4.js";
131
- import "./chunk-FAV25DUZ.js";
132
115
  import "./chunk-33JBK2XP.js";
133
116
  import "./chunk-TQNRI55H.js";
134
117
  import "./chunk-6GUG4YNM.js";
135
118
  import "./chunk-JXS5PDQ7.js";
136
- import "./chunk-6GDHLVJC.js";
119
+ import "./chunk-AV5ZEH4M.js";
137
120
  import "./chunk-BEUDU7Y4.js";
138
121
  import "./chunk-3PY7VHV7.js";
139
122
  import "./chunk-AJU4PJGY.js";
140
123
  import "./chunk-FZZ2QTJI.js";
141
- import "./chunk-3ONXXHQO.js";
142
124
  import "./chunk-LMDRGRJ2.js";
143
125
  import "./chunk-OKTXM5H4.js";
144
126
  import "./chunk-3UXOZBHV.js";
@@ -157,12 +139,28 @@ import "./chunk-KD3QD3A5.js";
157
139
  import "./chunk-6VF75M3X.js";
158
140
  import "./chunk-H3FZVNRN.js";
159
141
  import "./chunk-HQ6NIBL6.js";
160
- import "./chunk-PVGDJXVK.js";
161
142
  import "./chunk-OADWQ5CR.js";
162
143
  import "./chunk-7WV3F5DQ.js";
163
144
  import "./chunk-EDQVAMQI.js";
164
145
  import "./chunk-JGSKJHF7.js";
165
146
  import "./chunk-YQNADJCT.js";
147
+ import "./chunk-LASA46TQ.js";
148
+ import "./chunk-RAGXUVQW.js";
149
+ import "./chunk-JOASJWQR.js";
150
+ import "./chunk-RN7MUWON.js";
151
+ import "./chunk-3MAONBX3.js";
152
+ import "./chunk-RG3LBSGH.js";
153
+ import "./chunk-CYEPCZN5.js";
154
+ import "./chunk-DCGT4FPP.js";
155
+ import "./chunk-Q4CAQGKQ.js";
156
+ import "./chunk-CINZGPSJ.js";
157
+ import "./chunk-ZFXCQPNO.js";
158
+ import "./chunk-Q6YIJGXJ.js";
159
+ import "./chunk-ORFGK3XI.js";
160
+ import "./chunk-OIF36KGD.js";
161
+ import "./chunk-7DTASS5T.js";
162
+ import "./chunk-DEDTFRH2.js";
163
+ import "./chunk-FAV25DUZ.js";
166
164
  import "./chunk-CI7RKSRE.js";
167
165
  import "./chunk-6HMYUWXR.js";
168
166
  import "./chunk-EI6V5UXY.js";
@@ -170,6 +168,7 @@ import "./chunk-QY7YA7OL.js";
170
168
  import "./chunk-NNVTUXEB.js";
171
169
  import "./chunk-QDW3E4RD.js";
172
170
  import "./chunk-H3PHZLMF.js";
171
+ import "./chunk-3ONXXHQO.js";
173
172
  import "./chunk-XL7FK7PJ.js";
174
173
  import "./chunk-2LSZVONP.js";
175
174
  import "./chunk-DEUNUKTD.js";
@@ -199,6 +198,7 @@ import "./chunk-JUC24CTX.js";
199
198
  import "./chunk-TBBDFYXW.js";
200
199
  import "./chunk-ZBJMUXZH.js";
201
200
  import "./chunk-2ODBA7MQ.js";
201
+ import "./chunk-PVGDJXVK.js";
202
202
  import "./chunk-A2IYSXDQ.js";
203
203
  import "./chunk-LPSF4OQH.js";
204
204
  import "./chunk-BJMBJZ2Y.js";
package/dist/qmd.js CHANGED
@@ -11,13 +11,13 @@ import {
11
11
  resolveQmdCapabilities,
12
12
  shouldAutoUpgradeQmd,
13
13
  versionAtLeast
14
- } from "./chunk-TZVQQTG4.js";
14
+ } from "./chunk-DEDTFRH2.js";
15
15
  import "./chunk-FAV25DUZ.js";
16
- import "./chunk-PVGDJXVK.js";
17
16
  import "./chunk-NNVTUXEB.js";
18
17
  import "./chunk-CPPS65WS.js";
19
18
  import "./chunk-JUC24CTX.js";
20
19
  import "./chunk-2ODBA7MQ.js";
20
+ import "./chunk-PVGDJXVK.js";
21
21
  import "./chunk-PZ5AY32C.js";
22
22
  export {
23
23
  QMD_SUPPORTED_VERSION,
@@ -7,10 +7,10 @@ import {
7
7
  runDirectAgent,
8
8
  runTemporalAgent,
9
9
  shouldRunAgent
10
- } from "./chunk-2ROPI5OE.js";
10
+ } from "./chunk-6GC5SGFE.js";
11
11
  import "./chunk-MGN7VHWQ.js";
12
- import "./chunk-PVGDJXVK.js";
13
12
  import "./chunk-2ODBA7MQ.js";
13
+ import "./chunk-PVGDJXVK.js";
14
14
  import "./chunk-PZ5AY32C.js";
15
15
  export {
16
16
  PARALLEL_AGENT_WEIGHTS,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  EmbedHelper
3
- } from "../chunk-EWLQPEO6.js";
3
+ } from "../chunk-RN7MUWON.js";
4
4
  import "../chunk-CINZGPSJ.js";
5
5
  import "../chunk-3ONXXHQO.js";
6
- import "../chunk-PVGDJXVK.js";
7
6
  import "../chunk-2ODBA7MQ.js";
7
+ import "../chunk-PVGDJXVK.js";
8
8
  import "../chunk-PZ5AY32C.js";
9
9
  export {
10
10
  EmbedHelper
@@ -2,9 +2,9 @@ import {
2
2
  createConversationIndexRuntime,
3
3
  createConversationSearchBackend,
4
4
  createSearchBackend
5
- } from "../chunk-YRMKDTKF.js";
5
+ } from "../chunk-RAGXUVQW.js";
6
6
  import "../chunk-JOASJWQR.js";
7
- import "../chunk-EWLQPEO6.js";
7
+ import "../chunk-RN7MUWON.js";
8
8
  import "../chunk-3MAONBX3.js";
9
9
  import "../chunk-RG3LBSGH.js";
10
10
  import "../chunk-CYEPCZN5.js";
@@ -15,14 +15,14 @@ import "../chunk-Q6YIJGXJ.js";
15
15
  import "../chunk-ORFGK3XI.js";
16
16
  import "../chunk-OIF36KGD.js";
17
17
  import "../chunk-7DTASS5T.js";
18
- import "../chunk-TZVQQTG4.js";
18
+ import "../chunk-DEDTFRH2.js";
19
19
  import "../chunk-FAV25DUZ.js";
20
- import "../chunk-3ONXXHQO.js";
21
- import "../chunk-PVGDJXVK.js";
22
20
  import "../chunk-NNVTUXEB.js";
21
+ import "../chunk-3ONXXHQO.js";
23
22
  import "../chunk-CPPS65WS.js";
24
23
  import "../chunk-JUC24CTX.js";
25
24
  import "../chunk-2ODBA7MQ.js";
25
+ import "../chunk-PVGDJXVK.js";
26
26
  import "../chunk-PZ5AY32C.js";
27
27
  export {
28
28
  createConversationIndexRuntime,
@@ -2,13 +2,13 @@ import {
2
2
  createConversationIndexRuntime,
3
3
  createConversationSearchBackend,
4
4
  createSearchBackend
5
- } from "../chunk-YRMKDTKF.js";
5
+ } from "../chunk-RAGXUVQW.js";
6
6
  import {
7
7
  RemoteSearchBackend
8
8
  } from "../chunk-JOASJWQR.js";
9
9
  import {
10
10
  EmbedHelper
11
- } from "../chunk-EWLQPEO6.js";
11
+ } from "../chunk-RN7MUWON.js";
12
12
  import {
13
13
  LanceDbBackend
14
14
  } from "../chunk-3MAONBX3.js";
@@ -29,14 +29,14 @@ import "../chunk-Q6YIJGXJ.js";
29
29
  import "../chunk-ORFGK3XI.js";
30
30
  import "../chunk-OIF36KGD.js";
31
31
  import "../chunk-7DTASS5T.js";
32
- import "../chunk-TZVQQTG4.js";
32
+ import "../chunk-DEDTFRH2.js";
33
33
  import "../chunk-FAV25DUZ.js";
34
- import "../chunk-3ONXXHQO.js";
35
- import "../chunk-PVGDJXVK.js";
36
34
  import "../chunk-NNVTUXEB.js";
35
+ import "../chunk-3ONXXHQO.js";
37
36
  import "../chunk-CPPS65WS.js";
38
37
  import "../chunk-JUC24CTX.js";
39
38
  import "../chunk-2ODBA7MQ.js";
39
+ import "../chunk-PVGDJXVK.js";
40
40
  import "../chunk-PZ5AY32C.js";
41
41
  export {
42
42
  EmbedHelper,
@@ -4,8 +4,8 @@ import {
4
4
  import "../chunk-Q4CAQGKQ.js";
5
5
  import "../chunk-CINZGPSJ.js";
6
6
  import "../chunk-FAV25DUZ.js";
7
- import "../chunk-PVGDJXVK.js";
8
7
  import "../chunk-2ODBA7MQ.js";
8
+ import "../chunk-PVGDJXVK.js";
9
9
  import "../chunk-PZ5AY32C.js";
10
10
  export {
11
11
  LanceDbBackend
@@ -4,8 +4,8 @@ import {
4
4
  import "../chunk-Q4CAQGKQ.js";
5
5
  import "../chunk-CINZGPSJ.js";
6
6
  import "../chunk-FAV25DUZ.js";
7
- import "../chunk-PVGDJXVK.js";
8
7
  import "../chunk-2ODBA7MQ.js";
8
+ import "../chunk-PVGDJXVK.js";
9
9
  import "../chunk-PZ5AY32C.js";
10
10
  export {
11
11
  MeilisearchBackend
@@ -5,8 +5,8 @@ import {
5
5
  import "../chunk-Q4CAQGKQ.js";
6
6
  import "../chunk-CINZGPSJ.js";
7
7
  import "../chunk-FAV25DUZ.js";
8
- import "../chunk-PVGDJXVK.js";
9
8
  import "../chunk-2ODBA7MQ.js";
9
+ import "../chunk-PVGDJXVK.js";
10
10
  import "../chunk-PZ5AY32C.js";
11
11
  export {
12
12
  OramaBackend,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.633",
3
+ "version": "9.3.635",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/access-mcp.ts CHANGED
@@ -1433,7 +1433,7 @@ export class EngramMcpServer {
1433
1433
  query: { type: "string" },
1434
1434
  namespace: { type: "string" },
1435
1435
  maxResults: { type: "number" },
1436
- collection: { type: "string", description: "QMD collection (omit for memory, 'global' for all)" },
1436
+ collection: { type: "string", description: "QMD collection. With namespaces enabled, omitted, base, and 'global' searches stay scoped to readable namespaces; namespace-derived collections require matching namespace access." },
1437
1437
  },
1438
1438
  required: ["query"],
1439
1439
  additionalProperties: false,
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import test from "node:test";
7
7
 
8
8
  import { EngramAccessInputError, EngramAccessService } from "./access-service.js";
9
+ import { namespaceCollectionName } from "./namespaces/search.js";
9
10
  import type { StorageManager } from "./storage.js";
10
11
  import type { PluginConfig } from "./types.js";
11
12
 
@@ -25,6 +26,8 @@ function makeConfig(): PluginConfig {
25
26
  daySum: { enabled: false },
26
27
  searchBackend: "orama",
27
28
  qmd: { enabled: false },
29
+ qmdCollection: "test-memory",
30
+ qmdMaxResults: 10,
28
31
  nativeKnowledge: { enabled: false },
29
32
  recall: { budget: {} },
30
33
  consolidation: { enabled: false },
@@ -50,14 +53,35 @@ function makeService(): {
50
53
  (service as unknown as {
51
54
  orchestrator: {
52
55
  config: PluginConfig;
56
+ qmd: {
57
+ search(query: string, collection?: string, maxResults?: number): Promise<unknown[]>;
58
+ searchGlobal(query: string, maxResults?: number): Promise<unknown[]>;
59
+ };
53
60
  getStorage(namespace: string): Promise<StorageManager>;
61
+ searchAcrossNamespaces(params: {
62
+ query: string;
63
+ namespaces?: string[];
64
+ maxResults?: number;
65
+ mode?: string;
66
+ }): Promise<unknown[]>;
54
67
  };
55
68
  }).orchestrator = {
56
69
  config: makeConfig(),
70
+ qmd: {
71
+ async search() {
72
+ throw new Error("qmd.search should not run in namespace mode");
73
+ },
74
+ async searchGlobal() {
75
+ throw new Error("qmd.searchGlobal should not run in namespace mode");
76
+ },
77
+ },
57
78
  async getStorage(namespace: string): Promise<StorageManager> {
58
79
  getStorageCalls.push(namespace);
59
80
  return storage;
60
81
  },
82
+ async searchAcrossNamespaces() {
83
+ return [];
84
+ },
61
85
  };
62
86
 
63
87
  return { service, storage, getStorageCalls };
@@ -126,6 +150,294 @@ test("memoryBrowse resolves namespace storage for read principals", async () =>
126
150
  assert.deepEqual(getStorageCalls, ["team"]);
127
151
  });
128
152
 
153
+ test("memorySearch without an explicit namespace uses readable recall namespaces", async () => {
154
+ const { service } = makeService();
155
+ let searchParams: unknown;
156
+ (service as unknown as {
157
+ orchestrator: {
158
+ searchAcrossNamespaces(params: unknown): Promise<Array<{ path: string; score: number; snippet: string }>>;
159
+ };
160
+ }).orchestrator.searchAcrossNamespaces = async (params) => {
161
+ searchParams = params;
162
+ return [{ path: "default/facts/a.md", score: 0.7, snippet: "matched" }];
163
+ };
164
+
165
+ const result = await service.memorySearch({
166
+ query: "deployment notes",
167
+ maxResults: 3,
168
+ principal: "reader",
169
+ });
170
+
171
+ assert.deepEqual(searchParams, {
172
+ query: "deployment notes",
173
+ namespaces: ["default", "shared"],
174
+ maxResults: 3,
175
+ mode: "search",
176
+ });
177
+ assert.equal(result.count, 1);
178
+ });
179
+
180
+ test("memorySearch with an explicit namespace searches only that readable namespace", async () => {
181
+ const { service } = makeService();
182
+ let searchParams: unknown;
183
+ (service as unknown as {
184
+ orchestrator: {
185
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
186
+ };
187
+ }).orchestrator.searchAcrossNamespaces = async (params) => {
188
+ searchParams = params;
189
+ return [];
190
+ };
191
+
192
+ await service.memorySearch({
193
+ query: "release note",
194
+ namespace: "team",
195
+ maxResults: 2,
196
+ principal: "reader",
197
+ });
198
+
199
+ assert.deepEqual(searchParams, {
200
+ query: "release note",
201
+ namespaces: ["team"],
202
+ maxResults: 2,
203
+ mode: "search",
204
+ });
205
+ });
206
+
207
+ test("memorySearch rejects unreadable namespaces before collection routing", async () => {
208
+ const { service } = makeService();
209
+ let searchCalls = 0;
210
+ (service as unknown as {
211
+ orchestrator: {
212
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
213
+ };
214
+ }).orchestrator.searchAcrossNamespaces = async () => {
215
+ searchCalls += 1;
216
+ return [];
217
+ };
218
+
219
+ await assert.rejects(
220
+ () =>
221
+ service.memorySearch({
222
+ query: "release note",
223
+ namespace: "team",
224
+ collection: namespaceCollectionName("test-memory", "team", {
225
+ defaultNamespace: "default",
226
+ useLegacyDefaultCollection: false,
227
+ }),
228
+ principal: "stranger",
229
+ }),
230
+ /namespace is not readable: team/,
231
+ );
232
+ assert.equal(searchCalls, 0);
233
+ });
234
+
235
+ test("memorySearch rejects empty collection names", async () => {
236
+ const { service } = makeService();
237
+ let searchCalls = 0;
238
+ (service as unknown as {
239
+ orchestrator: {
240
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
241
+ };
242
+ }).orchestrator.searchAcrossNamespaces = async () => {
243
+ searchCalls += 1;
244
+ return [];
245
+ };
246
+
247
+ await assert.rejects(
248
+ () =>
249
+ service.memorySearch({
250
+ query: "deployment notes",
251
+ collection: " ",
252
+ principal: "reader",
253
+ }),
254
+ /collection must be a non-empty string/,
255
+ );
256
+ assert.equal(searchCalls, 0);
257
+ });
258
+
259
+ test("memorySearch treats global collection as ACL-scoped when namespaces are enabled", async () => {
260
+ const { service } = makeService();
261
+ let globalSearchCalls = 0;
262
+ let searchParams: unknown;
263
+ (service as unknown as {
264
+ orchestrator: {
265
+ qmd: {
266
+ searchGlobal(query: string, maxResults?: number): Promise<unknown[]>;
267
+ };
268
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
269
+ };
270
+ }).orchestrator.qmd.searchGlobal = async () => {
271
+ globalSearchCalls += 1;
272
+ return [];
273
+ };
274
+ (service as unknown as {
275
+ orchestrator: {
276
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
277
+ };
278
+ }).orchestrator.searchAcrossNamespaces = async (params) => {
279
+ searchParams = params;
280
+ return [];
281
+ };
282
+
283
+ await service.memorySearch({
284
+ query: "runbook",
285
+ collection: "global",
286
+ principal: "reader",
287
+ });
288
+
289
+ assert.equal(globalSearchCalls, 0);
290
+ assert.deepEqual(searchParams, {
291
+ query: "runbook",
292
+ namespaces: ["default", "shared"],
293
+ maxResults: undefined,
294
+ mode: "search",
295
+ });
296
+ });
297
+
298
+ test("memorySearch accepts a namespace-scoped collection for the requested namespace", async () => {
299
+ const { service } = makeService();
300
+ let searchParams: unknown;
301
+ (service as unknown as {
302
+ orchestrator: {
303
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
304
+ };
305
+ }).orchestrator.searchAcrossNamespaces = async (params) => {
306
+ searchParams = params;
307
+ return [];
308
+ };
309
+
310
+ await service.memorySearch({
311
+ query: "release note",
312
+ namespace: "team",
313
+ collection: namespaceCollectionName("test-memory", "team", {
314
+ defaultNamespace: "default",
315
+ useLegacyDefaultCollection: false,
316
+ }),
317
+ principal: "reader",
318
+ });
319
+
320
+ assert.deepEqual(searchParams, {
321
+ query: "release note",
322
+ namespaces: ["team"],
323
+ maxResults: undefined,
324
+ mode: "search",
325
+ });
326
+ });
327
+
328
+ test("memorySearch accepts a readable namespace-scoped collection without duplicate namespace", async () => {
329
+ const { service } = makeService();
330
+ let searchParams: unknown;
331
+ (service as unknown as {
332
+ orchestrator: {
333
+ searchAcrossNamespaces(params: unknown): Promise<unknown[]>;
334
+ };
335
+ }).orchestrator.searchAcrossNamespaces = async (params) => {
336
+ searchParams = params;
337
+ return [];
338
+ };
339
+
340
+ await service.memorySearch({
341
+ query: "release note",
342
+ collection: namespaceCollectionName("test-memory", "team", {
343
+ defaultNamespace: "default",
344
+ useLegacyDefaultCollection: false,
345
+ }),
346
+ principal: "reader",
347
+ });
348
+
349
+ assert.deepEqual(searchParams, {
350
+ query: "release note",
351
+ namespaces: ["team"],
352
+ maxResults: undefined,
353
+ mode: "search",
354
+ });
355
+ });
356
+
357
+ test("memorySearch rejects arbitrary custom collections when namespaces are enabled", async () => {
358
+ const { service } = makeService();
359
+ let qmdSearchCalls = 0;
360
+ (service as unknown as {
361
+ orchestrator: {
362
+ qmd: {
363
+ search(query: string, collection?: string, maxResults?: number): Promise<unknown[]>;
364
+ };
365
+ };
366
+ }).orchestrator.qmd.search = async () => {
367
+ qmdSearchCalls += 1;
368
+ return [];
369
+ };
370
+
371
+ await assert.rejects(
372
+ () =>
373
+ service.memorySearch({
374
+ query: "deployment notes",
375
+ collection: "custom-collection",
376
+ principal: "reader",
377
+ }),
378
+ /collection is not namespace-scoped for the requested principal/,
379
+ );
380
+ assert.equal(qmdSearchCalls, 0);
381
+ });
382
+
383
+ test("memorySearch honors custom collections when namespaces are disabled", async () => {
384
+ const { service } = makeService();
385
+ (service as unknown as { orchestrator: { config: PluginConfig } }).orchestrator.config = {
386
+ ...makeConfig(),
387
+ namespacesEnabled: false,
388
+ };
389
+ let qmdSearchArgs: unknown[] | undefined;
390
+ (service as unknown as {
391
+ orchestrator: {
392
+ qmd: {
393
+ search(query: string, collection?: string, maxResults?: number): Promise<unknown[]>;
394
+ };
395
+ };
396
+ }).orchestrator.qmd.search = async (...args) => {
397
+ qmdSearchArgs = args;
398
+ return [{ path: "facts/a.md", score: 0.5, snippet: "matched" }];
399
+ };
400
+
401
+ const result = await service.memorySearch({
402
+ query: "deployment notes",
403
+ collection: "custom-collection",
404
+ maxResults: 4,
405
+ });
406
+
407
+ assert.deepEqual(qmdSearchArgs, ["deployment notes", "custom-collection", 4]);
408
+ assert.equal(result.count, 1);
409
+ });
410
+
411
+ test("memorySearch rejects unsupported namespaces when namespaces are disabled", async () => {
412
+ const { service } = makeService();
413
+ (service as unknown as { orchestrator: { config: PluginConfig } }).orchestrator.config = {
414
+ ...makeConfig(),
415
+ namespacesEnabled: false,
416
+ };
417
+ let qmdSearchCalls = 0;
418
+ (service as unknown as {
419
+ orchestrator: {
420
+ qmd: {
421
+ search(query: string, collection?: string, maxResults?: number): Promise<unknown[]>;
422
+ };
423
+ };
424
+ }).orchestrator.qmd.search = async () => {
425
+ qmdSearchCalls += 1;
426
+ return [];
427
+ };
428
+
429
+ await assert.rejects(
430
+ () =>
431
+ service.memorySearch({
432
+ query: "deployment notes",
433
+ namespace: "team",
434
+ collection: "custom-collection",
435
+ }),
436
+ /unsupported namespace: team/,
437
+ );
438
+ assert.equal(qmdSearchCalls, 0);
439
+ });
440
+
129
441
  test("offlineSyncFiles reports invalid requested paths as input errors", async () => {
130
442
  const { service } = makeService();
131
443
  (service as unknown as {