@remnic/core 9.46.1 → 9.46.2

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 (116) hide show
  1. package/dist/access-admin-ops-surface.d.ts +2 -2
  2. package/dist/access-admin-ops-surface.js +3 -2
  3. package/dist/access-authorization-probe.d.ts +2 -2
  4. package/dist/access-authorization-probe.js +4 -3
  5. package/dist/access-boundary.d.ts +2 -2
  6. package/dist/access-boundary.js +3 -2
  7. package/dist/access-cli.js +16 -16
  8. package/dist/access-extraction-force-flush.d.ts +2 -2
  9. package/dist/access-extraction-force-flush.js +3 -2
  10. package/dist/access-http-lcm-compaction.d.ts +2 -2
  11. package/dist/access-http-lcm-compaction.js +1 -1
  12. package/dist/access-http-lifecycle-flush.d.ts +2 -2
  13. package/dist/access-http-lifecycle-flush.js +2 -2
  14. package/dist/access-http-offline-stream.d.ts +2 -2
  15. package/dist/access-http-query.js +3 -2
  16. package/dist/access-http.d.ts +8 -2
  17. package/dist/access-http.js +11 -10
  18. package/dist/access-identity-continuity-surface.d.ts +1 -1
  19. package/dist/access-identity-continuity-surface.js +3 -2
  20. package/dist/access-lcm-surface.d.ts +2 -2
  21. package/dist/access-lcm-surface.js +3 -2
  22. package/dist/access-mcp.d.ts +2 -2
  23. package/dist/access-mcp.js +7 -6
  24. package/dist/access-memory-search-fanout.d.ts +163 -2
  25. package/dist/access-memory-search-fanout.js +20 -3
  26. package/dist/access-namespace-preflight.js +3 -2
  27. package/dist/access-observe-write-surface.d.ts +2 -2
  28. package/dist/access-observe-write-surface.js +3 -2
  29. package/dist/access-offline-manifest.d.ts +1 -1
  30. package/dist/access-operations-batch.js +4 -3
  31. package/dist/access-operations.d.ts +6 -5
  32. package/dist/access-operations.js +6 -5
  33. package/dist/access-recall-concurrency.d.ts +2 -2
  34. package/dist/access-recall-concurrency.js +3 -2
  35. package/dist/access-recall-response.d.ts +2 -2
  36. package/dist/access-recall-response.js +3 -2
  37. package/dist/access-recall-surface.d.ts +2 -2
  38. package/dist/access-recall-surface.js +3 -2
  39. package/dist/access-schema.d.ts +84 -84
  40. package/dist/{access-service-loJadYd7.d.ts → access-service-DNBYHK33.d.ts} +2 -1
  41. package/dist/access-service-helpers.d.ts +2 -2
  42. package/dist/access-service.d.ts +2 -2
  43. package/dist/access-service.js +3 -2
  44. package/dist/access-surface-catalog.d.ts +2 -2
  45. package/dist/access-surface-catalog.js +1 -0
  46. package/dist/access-surface-catalog.js.map +1 -1
  47. package/dist/active-recall.js +2 -2
  48. package/dist/bootstrap.d.ts +1 -1
  49. package/dist/{chunk-WHWDBTCJ.js → chunk-74HN5QUH.js} +2 -2
  50. package/dist/{chunk-2DSUT7DZ.js → chunk-FQU3HTQM.js} +40 -11
  51. package/dist/chunk-FQU3HTQM.js.map +1 -0
  52. package/dist/{chunk-5LRDM4R7.js → chunk-G2L5IZV4.js} +7 -7
  53. package/dist/{chunk-TQZ4DVBC.js → chunk-GZ3R6XOE.js} +5 -2
  54. package/dist/chunk-GZ3R6XOE.js.map +1 -0
  55. package/dist/{chunk-5546W5SI.js → chunk-HLKPRWF4.js} +2 -2
  56. package/dist/chunk-IXEBUYO4.js +201 -0
  57. package/dist/chunk-IXEBUYO4.js.map +1 -0
  58. package/dist/{chunk-RCU25Z66.js → chunk-IXH2U2QT.js} +169 -58
  59. package/dist/chunk-IXH2U2QT.js.map +1 -0
  60. package/dist/{chunk-HJ3SVHUW.js → chunk-IZQ5TDFT.js} +9 -4
  61. package/dist/chunk-IZQ5TDFT.js.map +1 -0
  62. package/dist/{chunk-RVPTS4U5.js → chunk-JCNJIXZN.js} +2 -2
  63. package/dist/{chunk-5R6S4DJZ.js → chunk-N5XULCU3.js} +2 -2
  64. package/dist/{chunk-6H744ZBH.js → chunk-QISYTKG4.js} +2 -2
  65. package/dist/{chunk-AUMWZSE7.js → chunk-RN53QBM4.js} +4 -4
  66. package/dist/{chunk-M32URQXV.js → chunk-SIP5DBDN.js} +275 -334
  67. package/dist/chunk-SIP5DBDN.js.map +1 -0
  68. package/dist/{chunk-44N6ESJ4.js → chunk-TIDEL77O.js} +2 -2
  69. package/dist/{chunk-XBMJ35IP.js → chunk-VFZS2HAA.js} +4 -3
  70. package/dist/chunk-VFZS2HAA.js.map +1 -0
  71. package/dist/{cli-D_RcqXfZ.d.ts → cli-BWXSPxB9.d.ts} +2 -2
  72. package/dist/cli.d.ts +3 -3
  73. package/dist/cli.js +15 -15
  74. package/dist/config.js +2 -2
  75. package/dist/explicit-capture.d.ts +1 -1
  76. package/dist/external-wiki-access.d.ts +2 -2
  77. package/dist/external-wiki-access.js +4 -3
  78. package/dist/external-wiki-mcp-tools.d.ts +2 -2
  79. package/dist/index.d.ts +680 -680
  80. package/dist/index.js +16 -16
  81. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  82. package/dist/operator-toolkit.js +3 -3
  83. package/dist/{orchestrator-LPGegii3.d.ts → orchestrator-CRR-6sVB.d.ts} +6 -0
  84. package/dist/orchestrator.d.ts +1 -1
  85. package/dist/orchestrator.js +17 -17
  86. package/dist/resume-bundles.js +3 -3
  87. package/dist/schemas.d.ts +76 -76
  88. package/dist/shared-context/manager.d.ts +8 -8
  89. package/dist/transfer/types.d.ts +66 -66
  90. package/package.json +2 -2
  91. package/src/access-http-lcm-compaction.ts +3 -0
  92. package/src/access-http.test.ts +645 -0
  93. package/src/access-http.ts +55 -4
  94. package/src/access-mcp.ts +1 -0
  95. package/src/access-memory-search-fanout.ts +305 -2
  96. package/src/access-operations.ts +6 -0
  97. package/src/access-service-namespace.test.ts +146 -0
  98. package/src/access-service.ts +39 -40
  99. package/src/access-surface-catalog.ts +1 -0
  100. package/src/orchestration/generic-recall-paths.ts +116 -5
  101. package/dist/chunk-2DSUT7DZ.js.map +0 -1
  102. package/dist/chunk-7HXCF4RE.js +0 -72
  103. package/dist/chunk-7HXCF4RE.js.map +0 -1
  104. package/dist/chunk-HJ3SVHUW.js.map +0 -1
  105. package/dist/chunk-M32URQXV.js.map +0 -1
  106. package/dist/chunk-RCU25Z66.js.map +0 -1
  107. package/dist/chunk-TQZ4DVBC.js.map +0 -1
  108. package/dist/chunk-XBMJ35IP.js.map +0 -1
  109. /package/dist/{chunk-WHWDBTCJ.js.map → chunk-74HN5QUH.js.map} +0 -0
  110. /package/dist/{chunk-5LRDM4R7.js.map → chunk-G2L5IZV4.js.map} +0 -0
  111. /package/dist/{chunk-5546W5SI.js.map → chunk-HLKPRWF4.js.map} +0 -0
  112. /package/dist/{chunk-RVPTS4U5.js.map → chunk-JCNJIXZN.js.map} +0 -0
  113. /package/dist/{chunk-5R6S4DJZ.js.map → chunk-N5XULCU3.js.map} +0 -0
  114. /package/dist/{chunk-6H744ZBH.js.map → chunk-QISYTKG4.js.map} +0 -0
  115. /package/dist/{chunk-AUMWZSE7.js.map → chunk-RN53QBM4.js.map} +0 -0
  116. /package/dist/{chunk-44N6ESJ4.js.map → chunk-TIDEL77O.js.map} +0 -0
@@ -94,7 +94,7 @@ import {
94
94
  buildResumeBundleFromState,
95
95
  getResumeBundleStatus,
96
96
  recordResumeBundle
97
- } from "./chunk-5546W5SI.js";
97
+ } from "./chunk-HLKPRWF4.js";
98
98
  import {
99
99
  getWorkProductLedgerStatus,
100
100
  recordWorkProductLedgerEntry,
@@ -118,7 +118,7 @@ import {
118
118
  runOperatorInventory,
119
119
  runOperatorRepair,
120
120
  runOperatorSetup
121
- } from "./chunk-5R6S4DJZ.js";
121
+ } from "./chunk-N5XULCU3.js";
122
122
  import {
123
123
  analyzeSessionIntegrity,
124
124
  applySessionRepair,
@@ -196,11 +196,11 @@ import {
196
196
  } from "./chunk-UQ7RN5HK.js";
197
197
  import {
198
198
  parseConfig
199
- } from "./chunk-AUMWZSE7.js";
199
+ } from "./chunk-RN53QBM4.js";
200
200
  import {
201
201
  EngramAccessHttpServer,
202
202
  MeetingsInputError
203
- } from "./chunk-2DSUT7DZ.js";
203
+ } from "./chunk-FQU3HTQM.js";
204
204
  import {
205
205
  WearablesInputError
206
206
  } from "./chunk-V7IZKJI3.js";
@@ -213,13 +213,13 @@ import {
213
213
  createProductionChatLlmAdapter,
214
214
  loadChatSession,
215
215
  sessionBelongsToPrincipal
216
- } from "./chunk-XBMJ35IP.js";
216
+ } from "./chunk-VFZS2HAA.js";
217
217
  import {
218
218
  getAbstractionNodeStoreStatus
219
219
  } from "./chunk-OADWQ5CR.js";
220
220
  import {
221
221
  EngramAccessService
222
- } from "./chunk-RCU25Z66.js";
222
+ } from "./chunk-IXH2U2QT.js";
223
223
  import {
224
224
  WorkStorage
225
225
  } from "./chunk-GDB4J2H3.js";
@@ -7985,4 +7985,4 @@ export {
7985
7985
  listMemoryMarkdownFilePaths,
7986
7986
  registerCli
7987
7987
  };
7988
- //# sourceMappingURL=chunk-5LRDM4R7.js.map
7988
+ //# sourceMappingURL=chunk-G2L5IZV4.js.map
@@ -4,7 +4,10 @@ function respondAccessCapabilitiesHttp(response) {
4
4
  response.setHeader("content-type", "application/json; charset=utf-8");
5
5
  response.end(JSON.stringify({
6
6
  lcmCompactionFlushBatch: true,
7
- offlineSyncConvergenceComplete: true
7
+ offlineSyncConvergenceComplete: true,
8
+ // POST /engram|remnic/v1/memories/search — HTTP clients probe this before
9
+ // using the route so they can degrade against an older daemon.
10
+ memoriesSearch: true
8
11
  }, null, 2));
9
12
  }
10
13
  async function runLcmCompactionFlushHttp({
@@ -130,4 +133,4 @@ export {
130
133
  handleLcmCompactionFlushHttp,
131
134
  runLcmCompactionRecordHttp
132
135
  };
133
- //# sourceMappingURL=chunk-TQZ4DVBC.js.map
136
+ //# sourceMappingURL=chunk-GZ3R6XOE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/access-http-lcm-compaction.ts"],"sourcesContent":["import type { ServerResponse } from \"node:http\";\n\nimport type { LcmCompactionFlushRequest, LcmCompactionRecordRequest } from \"./access-schema.js\";\nimport type {\n EngramAccessLcmCompactionFlushResponse,\n EngramAccessLcmCompactionRecordResponse,\n EngramAccessService,\n} from \"./access-service.js\";\n\ntype LcmCompactionService = Pick<EngramAccessService, \"lcmCompactionFlush\" | \"lcmCompactionRecord\">;\ntype JsonResponder = (res: ServerResponse, status: number, payload: unknown) => void;\n\nexport interface LcmCompactionFlushHttpOptions {\n body: LcmCompactionFlushRequest;\n service: LcmCompactionService;\n response: ServerResponse;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n defaultNamespace?: string;\n resolveRequestPrincipal: () => string | undefined;\n respondJson: JsonResponder;\n}\n\nexport function respondAccessCapabilitiesHttp(response: ServerResponse): void {\n response.statusCode = 200;\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify({\n lcmCompactionFlushBatch: true,\n offlineSyncConvergenceComplete: true,\n // POST /engram|remnic/v1/memories/search — HTTP clients probe this before\n // using the route so they can degrade against an older daemon.\n memoriesSearch: true,\n }, null, 2));\n}\n\nexport interface LcmCompactionFlushRunnerOptions {\n body: LcmCompactionFlushRequest;\n service: LcmCompactionService;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n defaultNamespace?: string;\n resolveRequestPrincipal: () => string | undefined;\n}\n\ntype LcmCompactionFlushBatchResult =\n | {\n namespace: string;\n status: \"fulfilled\";\n result: EngramAccessLcmCompactionFlushResponse;\n }\n | { namespace: string; status: \"rejected\" };\n\ntype LcmCompactionFlushHttpResult =\n | EngramAccessLcmCompactionFlushResponse\n | {\n enabled: boolean;\n flushed: boolean;\n sessionKey: string;\n namespaces: string[];\n results: LcmCompactionFlushBatchResult[];\n };\n\nexport async function runLcmCompactionFlushHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n}: LcmCompactionFlushRunnerOptions): Promise<LcmCompactionFlushHttpResult> {\n ensureWriteRateLimitAvailable();\n const requestedNamespaces = body.namespaces;\n if (requestedNamespaces === undefined) {\n const result = await service.lcmCompactionFlush({\n sessionKey: body.sessionKey,\n namespace: resolveNamespace(body.namespace),\n ...(body.cwd !== undefined ? { cwd: body.cwd } : {}),\n ...(body.projectTag !== undefined ? { projectTag: body.projectTag } : {}),\n authenticatedPrincipal: resolveRequestPrincipal(),\n });\n recordWriteRateLimitHit();\n return result;\n }\n const resolutionOutcomes = await Promise.allSettled(\n requestedNamespaces.map(async (requestedNamespace) => {\n const namespace = resolveNamespace(requestedNamespace);\n return {\n requestedNamespace,\n namespace,\n effectiveNamespace: namespace ?? defaultNamespace ?? \"\",\n };\n })\n );\n const uniqueResolvedNamespaces: Array<{\n requestedNamespace: string;\n namespace: string | undefined;\n effectiveNamespace: string;\n }> = [];\n const queuedEffectiveNamespaces = new Set<string>();\n for (const outcome of resolutionOutcomes) {\n if (outcome.status === \"fulfilled\" && !queuedEffectiveNamespaces.has(outcome.value.effectiveNamespace)) {\n queuedEffectiveNamespaces.add(outcome.value.effectiveNamespace);\n uniqueResolvedNamespaces.push(outcome.value);\n }\n }\n const serviceOutcomes = await Promise.allSettled(\n uniqueResolvedNamespaces.map(({ namespace }) =>\n service.lcmCompactionFlush({\n sessionKey: body.sessionKey,\n namespace,\n ...(body.cwd !== undefined ? { cwd: body.cwd } : {}),\n ...(body.projectTag !== undefined ? { projectTag: body.projectTag } : {}),\n authenticatedPrincipal: resolveRequestPrincipal(),\n })\n )\n );\n const serviceOutcomesByEffectiveNamespace = new Map<\n string,\n PromiseSettledResult<EngramAccessLcmCompactionFlushResponse>\n >();\n for (const [index, resolved] of uniqueResolvedNamespaces.entries()) {\n const serviceOutcome = serviceOutcomes[index];\n if (serviceOutcome !== undefined) {\n serviceOutcomesByEffectiveNamespace.set(resolved.effectiveNamespace, serviceOutcome);\n }\n }\n const results = resolutionOutcomes.map((outcome, index): LcmCompactionFlushBatchResult => {\n const requestedNamespace = requestedNamespaces[index] ?? \"\";\n if (outcome.status === \"rejected\") {\n return { status: \"rejected\", namespace: requestedNamespace };\n }\n const serviceOutcome = serviceOutcomesByEffectiveNamespace.get(outcome.value.effectiveNamespace);\n return serviceOutcome?.status === \"fulfilled\"\n ? { status: \"fulfilled\", namespace: requestedNamespace, result: serviceOutcome.value }\n : { status: \"rejected\", namespace: requestedNamespace };\n });\n recordWriteRateLimitHit();\n return {\n enabled: results.every((result) => result.status === \"fulfilled\" && result.result.enabled !== false),\n flushed: results.every((result) => result.status === \"fulfilled\" && result.result.flushed !== false),\n sessionKey: body.sessionKey,\n namespaces: requestedNamespaces,\n results,\n };\n}\nexport async function handleLcmCompactionFlushHttp({\n body,\n service,\n response,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n respondJson,\n}: LcmCompactionFlushHttpOptions): Promise<void> {\n const result = await runLcmCompactionFlushHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n });\n respondJson(response, 200, result);\n}\n\nexport interface LcmCompactionRecordHttpOptions {\n body: LcmCompactionRecordRequest;\n service: LcmCompactionService;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n resolveRequestPrincipal: () => string | undefined;\n}\n\nexport async function runLcmCompactionRecordHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n resolveRequestPrincipal,\n}: LcmCompactionRecordHttpOptions): Promise<EngramAccessLcmCompactionRecordResponse> {\n ensureWriteRateLimitAvailable();\n const response = await service.lcmCompactionRecord({\n sessionKey: body.sessionKey,\n namespace: resolveNamespace(body.namespace),\n tokensBefore: body.tokensBefore,\n tokensAfter: body.tokensAfter,\n authenticatedPrincipal: resolveRequestPrincipal(),\n });\n recordWriteRateLimitHit();\n return response;\n}\n"],"mappings":"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","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-LMDRGRJ2.js";
12
12
  import {
13
13
  parseConfig
14
- } from "./chunk-AUMWZSE7.js";
14
+ } from "./chunk-RN53QBM4.js";
15
15
  import {
16
16
  resolveObjectiveStateStoreDir,
17
17
  validateObjectiveStateSnapshot
@@ -268,4 +268,4 @@ export {
268
268
  recordResumeBundle,
269
269
  getResumeBundleStatus
270
270
  };
271
- //# sourceMappingURL=chunk-5546W5SI.js.map
271
+ //# sourceMappingURL=chunk-HLKPRWF4.js.map
@@ -0,0 +1,201 @@
1
+ import {
2
+ canReadNamespace,
3
+ defaultNamespaceForPrincipal
4
+ } from "./chunk-3GD2HFR6.js";
5
+ import {
6
+ EngramAccessInputError
7
+ } from "./chunk-2Z24E5W2.js";
8
+ import {
9
+ log
10
+ } from "./chunk-3WCASNQZ.js";
11
+
12
+ // src/access-memory-search-fanout.ts
13
+ async function resolveMemorySearchDefaultFallback(options) {
14
+ const { profilePlan, config, principal } = options;
15
+ const principalSelfIsDefault = defaultNamespaceForPrincipal(principal, config) === config.defaultNamespace;
16
+ const profileIntendsUserGlobal = profilePlan.profile.readOrder.includes("userGlobal");
17
+ const userGlobalLayerReadable = profilePlan.layers.some(
18
+ (layer) => layer.id === "userGlobal" && layer.readable && Boolean(layer.namespace)
19
+ );
20
+ const selfResolvedAwayFromDefault = profilePlan.baseNamespace !== config.defaultNamespace;
21
+ if (!(profileIntendsUserGlobal && userGlobalLayerReadable && principalSelfIsDefault && selfResolvedAwayFromDefault && canReadNamespace(principal, config.defaultNamespace, config))) {
22
+ return null;
23
+ }
24
+ const defaultAtFlatRoot = await options.defaultAtFlatRootProvider();
25
+ return defaultAtFlatRoot ? config.defaultNamespace : null;
26
+ }
27
+ async function defaultNamespaceAtFlatRoot(getStorage, config) {
28
+ const storage = await getStorage(config.defaultNamespace);
29
+ return storage.dir === config.memoryDir;
30
+ }
31
+ function mergeMemorySearchDefaultFallback(profileNamespaces, fallback) {
32
+ if (!fallback || profileNamespaces.includes(fallback)) return profileNamespaces;
33
+ return [...profileNamespaces, fallback];
34
+ }
35
+ async function runMemorySearchFanout(options) {
36
+ const { query, namespaces, maxResults, principal, requestedNamespace, collection } = options;
37
+ if (namespaces.length === 0) {
38
+ log.warn("memory_search resolved zero readable namespaces", {
39
+ principal: principal ?? null,
40
+ requestedNamespace: requestedNamespace ?? null,
41
+ collection: collection ?? null
42
+ });
43
+ return [];
44
+ }
45
+ log.debug("memory_search fanout", {
46
+ principal: principal ?? null,
47
+ namespaces,
48
+ collection: collection ?? null
49
+ });
50
+ const degradations = [];
51
+ const results = await options.search({
52
+ query,
53
+ namespaces,
54
+ maxResults,
55
+ mode: options.mode ?? "search",
56
+ execution: {
57
+ onDegradation: (degradation) => degradations.push(degradation)
58
+ }
59
+ });
60
+ if (degradations.length > 0) {
61
+ log.warn("memory_search backend degradation", {
62
+ principal: principal ?? null,
63
+ degradations
64
+ });
65
+ }
66
+ return results;
67
+ }
68
+ var MEMORY_SEARCH_MODES = ["search", "hybrid", "bm25", "vector"];
69
+ function assertMemorySearchLimit(maxResults) {
70
+ if (maxResults === void 0) return;
71
+ if (typeof maxResults !== "number" || !Number.isInteger(maxResults) || maxResults < 0) {
72
+ throw new EngramAccessInputError(
73
+ `maxResults must be a non-negative integer (got ${JSON.stringify(maxResults)})`
74
+ );
75
+ }
76
+ }
77
+ function assertMemorySearchMode(mode) {
78
+ if (mode === void 0) return;
79
+ if (typeof mode !== "string" || !MEMORY_SEARCH_MODES.includes(mode)) {
80
+ throw new EngramAccessInputError(
81
+ `mode must be one of ${MEMORY_SEARCH_MODES.join(", ")} (got ${JSON.stringify(mode)})`
82
+ );
83
+ }
84
+ }
85
+ function assertFlatCorpusOptions(mode, collection) {
86
+ assertMemorySearchMode(mode);
87
+ if (mode && collection) {
88
+ throw new EngramAccessInputError(
89
+ `mode is not supported together with collection on a flat corpus (got collection: ${collection})`
90
+ );
91
+ }
92
+ }
93
+ async function runFlatCorpusMemorySearch(options) {
94
+ const { query, maxResults, collection, mode } = options;
95
+ assertFlatCorpusOptions(options.mode, options.collection);
96
+ if (mode) {
97
+ return options.searchAcrossNamespaces({ query, maxResults, mode });
98
+ }
99
+ return collection === "global" ? options.searchGlobal(query, maxResults) : options.search(query, collection, maxResults);
100
+ }
101
+ var MEMORY_SEARCH_CANDIDATE_CAP = 25e3;
102
+ async function searchWithGenericExclusion(options) {
103
+ const { budget } = options;
104
+ if (budget <= 0) return [];
105
+ let results = [];
106
+ let limit = options.sendInitialLimit ? budget : void 0;
107
+ let target = budget;
108
+ let firstPage = true;
109
+ for (; ; ) {
110
+ const raw = await options.search(limit);
111
+ results = raw.filter((hit) => !options.isExcluded(hit.path));
112
+ if (firstPage) {
113
+ firstPage = false;
114
+ if (!options.sendInitialLimit) target = Math.min(budget, raw.length);
115
+ if (target <= 0) return [];
116
+ }
117
+ if (results.length >= target) break;
118
+ const served = limit ?? raw.length;
119
+ if (raw.length === 0 || raw.length < served) break;
120
+ const cap = Math.max(MEMORY_SEARCH_CANDIDATE_CAP, target * 2);
121
+ if (served >= cap) break;
122
+ limit = Math.min(served * 2, cap);
123
+ }
124
+ return results.slice(0, target);
125
+ }
126
+ async function runScopedMemorySearch(options) {
127
+ await options.authorizeScope();
128
+ const results = await searchWithGenericExclusion({
129
+ budget: options.budget,
130
+ sendInitialLimit: options.sendInitialLimit,
131
+ isExcluded: options.isExcluded,
132
+ search: (limit) => options.namespacesEnabled ? options.namespaced(limit) : options.flatCorpus(limit)
133
+ });
134
+ return results.map((hit) => ({
135
+ path: hit.path,
136
+ score: hit.score,
137
+ snippet: (hit.snippet ?? "").slice(0, 800)
138
+ }));
139
+ }
140
+ async function memorySearchThroughScope(deps, request) {
141
+ const { query, namespace, maxResults, mode, principal } = request;
142
+ assertMemorySearchMode(mode);
143
+ assertMemorySearchLimit(maxResults);
144
+ const collection = request.collection?.trim();
145
+ if (request.collection !== void 0 && !collection) {
146
+ throw new EngramAccessInputError("collection must be a non-empty string");
147
+ }
148
+ let searchNamespaces = [];
149
+ const results = await runScopedMemorySearch({
150
+ query,
151
+ collection,
152
+ mode,
153
+ namespacesEnabled: deps.namespacesEnabled,
154
+ isExcluded: deps.isExcluded,
155
+ budget: maxResults ?? deps.defaultBudget,
156
+ sendInitialLimit: maxResults !== void 0,
157
+ authorizeScope: async () => {
158
+ if (!deps.namespacesEnabled) {
159
+ assertFlatCorpusOptions(mode, collection);
160
+ return deps.authorizeFlatCorpus(namespace, principal);
161
+ }
162
+ searchNamespaces = await deps.authorizeNamespaces(namespace, principal, collection);
163
+ },
164
+ flatCorpus: (limit) => runFlatCorpusMemorySearch({
165
+ query,
166
+ maxResults: limit,
167
+ collection,
168
+ mode,
169
+ searchAcrossNamespaces: (p) => deps.searchAcrossNamespaces(p),
170
+ searchGlobal: (q, globalLimit) => deps.searchGlobal(q, globalLimit),
171
+ search: (q, coll, searchLimit) => deps.search(q, coll, searchLimit)
172
+ }),
173
+ namespaced: (limit) => runMemorySearchFanout({
174
+ query,
175
+ maxResults: limit,
176
+ principal,
177
+ collection,
178
+ mode,
179
+ requestedNamespace: namespace,
180
+ namespaces: searchNamespaces,
181
+ search: (p) => deps.searchAcrossNamespaces(p)
182
+ })
183
+ });
184
+ return { query, results, count: results.length };
185
+ }
186
+
187
+ export {
188
+ resolveMemorySearchDefaultFallback,
189
+ defaultNamespaceAtFlatRoot,
190
+ mergeMemorySearchDefaultFallback,
191
+ runMemorySearchFanout,
192
+ MEMORY_SEARCH_MODES,
193
+ assertMemorySearchLimit,
194
+ assertMemorySearchMode,
195
+ assertFlatCorpusOptions,
196
+ runFlatCorpusMemorySearch,
197
+ searchWithGenericExclusion,
198
+ runScopedMemorySearch,
199
+ memorySearchThroughScope
200
+ };
201
+ //# sourceMappingURL=chunk-IXEBUYO4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/access-memory-search-fanout.ts"],"sourcesContent":["import { canReadNamespace, defaultNamespaceForPrincipal } from \"./namespaces/principal.js\";\nimport type { ResolvedScopeProfilePlan } from \"./namespaces/scope-profiles.js\";\nimport { EngramAccessInputError } from \"./access-errors.js\";\nimport { log } from \"./logger.js\";\nimport type { SearchDegradation, SearchExecutionOptions } from \"./search/port.js\";\nimport type { PluginConfig } from \"./types.js\";\n\n/**\n * Issue #2018: `memory_search` has no sessionKey, so — unlike recall — it\n * cannot resolve a coding overlay. With scope profiles active and no overlay,\n * `expandScopeProfileReadNamespaces` derives a per-principal self namespace.\n * On a legacy flat-root deployment the corpus still lives under the configured\n * DEFAULT namespace's base collection, which that derived set excludes, so\n * every query silently returned 0.\n *\n * Returns the configured default namespace to merge into the profile read set\n * when the base collection must stay reachable, or `null` otherwise. ALL of:\n *\n * - the default namespace is THIS principal's own self store — i.e. the\n * principal has no dedicated namespace policy, so\n * `defaultNamespaceForPrincipal` resolves to the configured default. On a\n * multi-tenant scope-profile deployment every principal has its own\n * policy; reaching into the default would read ANOTHER namespace, so the\n * fallback must not fire (#2056 r2 / #1501 privateOnly);\n * - the profile resolved `userGlobal` as a READABLE layer (not just listed\n * it in `readOrder`). `serverShared` maps to `sharedNamespace`, not the\n * default, and an unreadable resolved layer is a deliberate omission —\n * neither may trigger this fallback (#2056 r2);\n * - the profile's resolved self namespace is NOT already the default\n * (otherwise it is already in the set);\n * - the principal is authorized to read the default namespace.\n *\n * The flat-root signal is resolved lazily via {@link defaultAtFlatRootProvider}\n * — the provider is only invoked once the cheap gates (readOrder consent,\n * resolved-layer readability, principal-self, self-resolved-away, ACL) all\n * pass, so a project-only / serverShared-only / unreadable-userGlobal\n * profile never touches the default store (#2056 r6).\n */\nexport async function resolveMemorySearchDefaultFallback(options: {\n profilePlan: ResolvedScopeProfilePlan;\n config: PluginConfig;\n principal: string | undefined;\n /**\n * True only when the configured default namespace's storage root equals\n * `config.memoryDir` (the legacy flat-root layout). Invoked ONLY after the\n * cheap gates pass, so ineligible profiles never probe the default store.\n */\n defaultAtFlatRootProvider: () => Promise<boolean>;\n}): Promise<string | null> {\n const { profilePlan, config, principal } = options;\n // Defense-in-depth: principalSelfIsDefault is only ever true alongside a\n // flat-root default on legacy deployments (a hosted scope-profile principal\n // with its own policy fails this even when default is at the flat root). It\n // is NOT redundant — it keeps a policy-having principal isolated from the\n // flat default corpus.\n const principalSelfIsDefault =\n defaultNamespaceForPrincipal(principal, config) === config.defaultNamespace;\n // readOrder must explicitly intend userGlobal (resolveScopeProfilePlan\n // always materializes a userGlobal layer even when readOrder omits it, so\n // the layer's presence alone is not consent), AND that layer must have\n // resolved readable (a listed-but-unreadable layer is a deliberate\n // omission). serverShared maps to sharedNamespace, not the default.\n const profileIntendsUserGlobal =\n profilePlan.profile.readOrder.includes(\"userGlobal\");\n const userGlobalLayerReadable = profilePlan.layers.some(\n (layer) => layer.id === \"userGlobal\" && layer.readable && Boolean(layer.namespace),\n );\n const selfResolvedAwayFromDefault =\n profilePlan.baseNamespace !== config.defaultNamespace;\n if (\n !(\n profileIntendsUserGlobal &&\n userGlobalLayerReadable &&\n principalSelfIsDefault &&\n selfResolvedAwayFromDefault &&\n canReadNamespace(principal, config.defaultNamespace, config)\n )\n ) {\n // Cheap gates failed — never probe the default store.\n return null;\n }\n const defaultAtFlatRoot = await options.defaultAtFlatRootProvider();\n return defaultAtFlatRoot ? config.defaultNamespace : null;\n}\n\n/**\n * Issue #2018 r4: the explicit legacy-flat-root signal — the configured\n * default namespace's storage root equals `config.memoryDir`. Resolved from\n * storage (not inferred from config/policy shape) so a hosted scope-profile\n * deployment never reaches into the default corpus.\n */\nexport async function defaultNamespaceAtFlatRoot(\n getStorage: (namespace: string) => Promise<{ dir: string }>,\n config: { defaultNamespace: string; memoryDir: string },\n): Promise<boolean> {\n const storage = await getStorage(config.defaultNamespace);\n return storage.dir === config.memoryDir;\n}\n\n/**\n * Merge the {@link resolveMemorySearchDefaultFallback} result into a profile\n * read-namespace set without duplicates. Returns the original set unchanged\n * when no fallback applies.\n */\nexport function mergeMemorySearchDefaultFallback(\n profileNamespaces: string[],\n fallback: string | null,\n): string[] {\n if (!fallback || profileNamespaces.includes(fallback)) return profileNamespaces;\n return [...profileNamespaces, fallback];\n}\n\n\n/**\n * Run a memory_search namespace fanout with the observability required by\n * issue #2018: a silent empty fanout is surfaced as a warning (never silently\n * empty), and per-namespace backend degradations — which memory_search\n * previously swallowed — are collected and warned so a missing collection\n * looks different from a genuine no-matches.\n *\n * `search` is injected so this stays free of the orchestrator type.\n */\nexport async function runMemorySearchFanout<TResult>(options: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n principal?: string;\n requestedNamespace?: string;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n search(params: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n mode: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n execution: SearchExecutionOptions;\n }): Promise<TResult[]>;\n}): Promise<TResult[]> {\n const { query, namespaces, maxResults, principal, requestedNamespace, collection } = options;\n if (namespaces.length === 0) {\n log.warn(\"memory_search resolved zero readable namespaces\", {\n principal: principal ?? null,\n requestedNamespace: requestedNamespace ?? null,\n collection: collection ?? null,\n });\n return [];\n }\n log.debug(\"memory_search fanout\", {\n principal: principal ?? null,\n namespaces,\n collection: collection ?? null,\n });\n const degradations: SearchDegradation[] = [];\n const results = await options.search({\n query,\n namespaces,\n maxResults,\n mode: options.mode ?? \"search\",\n execution: {\n onDegradation: (degradation) => degradations.push(degradation),\n },\n });\n if (degradations.length > 0) {\n log.warn(\"memory_search backend degradation\", {\n principal: principal ?? null,\n degradations,\n });\n }\n return results;\n}\n\n/**\n * Resolve results for a FLAT corpus (namespaces disabled).\n *\n * An explicit ranking mode routes through the namespace-aware search even\n * here, because that is the only path honoring it; the legacy direct-QMD calls\n * stay the default so nothing else moves.\n */\n/**\n * Reject an option combination a flat corpus cannot honor.\n *\n * A `mode` routes through the namespace-aware backend, which cannot target a\n * specific collection, so pairing the two would silently search the default\n * collection and return unrelated results (AGENTS.md pattern 39).\n *\n * Exported as its own step because callers validate BEFORE any budget\n * short-circuit: changing only the requested result count must never make an\n * invalid request succeed.\n */\nexport const MEMORY_SEARCH_MODES = [\"search\", \"hybrid\", \"bm25\", \"vector\"] as const;\n\nexport type MemorySearchMode = (typeof MEMORY_SEARCH_MODES)[number];\n\n/**\n * Reject an unrecognized ranking mode at the service boundary.\n *\n * The HTTP and MCP schemas already validate their own input, but an\n * in-process caller reaches this service untyped. Both namespace backends\n * route an unknown mode through their DEFAULT ordinary-search branch, so a\n * typo would silently rank differently instead of reporting bad input\n * (AGENTS.md §39).\n */\n/**\n * Reject a budget that is not a finite non-negative integer.\n *\n * The HTTP and MCP schemas validate their own input, but an in-process caller\n * reaches this service untyped: a negative would hit the `budget <= 0`\n * short-circuit and return a successful EMPTY page, and a fraction or a\n * non-finite value would flow into the backend limit and top-up arithmetic.\n * Zero keeps its documented meaning — an empty result with no backend call.\n */\nexport function assertMemorySearchLimit(maxResults: unknown): void {\n if (maxResults === undefined) return;\n if (typeof maxResults !== \"number\" || !Number.isInteger(maxResults) || maxResults < 0) {\n throw new EngramAccessInputError(\n `maxResults must be a non-negative integer (got ${JSON.stringify(maxResults)})`,\n );\n }\n}\n\nexport function assertMemorySearchMode(mode: unknown): void {\n if (mode === undefined) return;\n if (typeof mode !== \"string\" || !(MEMORY_SEARCH_MODES as readonly string[]).includes(mode)) {\n throw new EngramAccessInputError(\n `mode must be one of ${MEMORY_SEARCH_MODES.join(\", \")} (got ${JSON.stringify(mode)})`,\n );\n }\n}\n\nexport function assertFlatCorpusOptions(\n mode: string | undefined,\n collection: string | undefined,\n): void {\n assertMemorySearchMode(mode);\n if (mode && collection) {\n throw new EngramAccessInputError(\n `mode is not supported together with collection on a flat corpus (got collection: ${collection})`,\n );\n }\n}\n\nexport async function runFlatCorpusMemorySearch<TResult>(options: {\n query: string;\n maxResults?: number;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n searchAcrossNamespaces(params: {\n query: string;\n maxResults?: number;\n mode: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n }): Promise<TResult[]>;\n searchGlobal(query: string, maxResults?: number): Promise<TResult[]>;\n search(query: string, collection: string | undefined, maxResults?: number): Promise<TResult[]>;\n}): Promise<TResult[]> {\n const { query, maxResults, collection, mode } = options;\n assertFlatCorpusOptions(options.mode, options.collection);\n if (mode) {\n // The mode-aware backend has no collection selector on a flat corpus, so\n // honoring the mode would silently search the default collection instead\n return options.searchAcrossNamespaces({ query, maxResults, mode });\n }\n return collection === \"global\"\n ? options.searchGlobal(query, maxResults)\n : options.search(query, collection, maxResults);\n}\n\n/**\n * How far one generic memory search may page before it stops.\n *\n * A backend-safety bound, NOT a stand-in for corpus exhaustion — which is why\n * it is ABSOLUTE rather than a multiple of the caller's budget. Scaling it to\n * the budget made \"the excluded paths happen to rank first\" indistinguishable\n * from \"there is nothing else\": a 1,000-row request whose first 4,000 hits are\n * artifacts stopped at 4,000 and returned an empty page while valid memories\n * sat at rank 4,001. Pages that come back FULL mean the corpus is not\n * exhausted, so the loop keeps going until a short page proves it is or this\n * cap protects the backend.\n */\nconst MEMORY_SEARCH_CANDIDATE_CAP = 25_000;\n\n\n/**\n * Run a ranked memory search and apply the generic-recall path exclusions\n * BEFORE the user-facing cap.\n *\n * Artifact isolation is a retrieval contract, not a ranking preference:\n * artifacts flow only through the dedicated verbatim path. Filtering after the\n * backend's own cap would let a handful of top-ranked artifacts shrink - or\n * empty - a page that has valid memories right behind them, so the search runs\n * with candidate headroom and tops up until the post-filter budget is met or\n * the corpus is exhausted.\n */\nexport async function searchWithGenericExclusion<TResult extends { path: string }>(options: {\n budget: number;\n /**\n * `false` omits `maxResults` on the FIRST request, so a caller that named no\n * budget keeps the backend's own page size on the wire; the resolved budget\n * is still what the filtered page is measured against.\n */\n sendInitialLimit: boolean;\n search(limit: number | undefined): Promise<TResult[]>;\n isExcluded(memoryPath: string): boolean;\n}): Promise<TResult[]> {\n const { budget } = options;\n if (budget <= 0) return [];\n let results: TResult[] = [];\n let limit: number | undefined = options.sendInitialLimit ? budget : undefined;\n // With no explicit limit the caller asked for the BACKEND's page, so that\n // page's size is the target — not the configured cap. Using the cap would\n // reissue the query whenever the backend's default page is smaller, and\n // return more rows than the same request used to.\n let target = budget;\n let firstPage = true;\n for (;;) {\n const raw = await options.search(limit);\n results = raw.filter((hit) => !options.isExcluded(hit.path));\n if (firstPage) {\n firstPage = false;\n if (!options.sendInitialLimit) target = Math.min(budget, raw.length);\n if (target <= 0) return [];\n }\n if (results.length >= target) break;\n // What the backend actually served this round: its own page size when we\n // named no limit.\n const served = limit ?? raw.length;\n // A short page means the corpus is exhausted - asking for more is wasted\n // work that returns the same rows.\n if (raw.length === 0 || raw.length < served) break;\n // The cap protects the backend from an unbounded walk, but it can never\n // sit at or below what the caller explicitly asked for: a request for N\n // rows needs room BEYOND N to replace the excluded hits among them, or a\n // large search returns a short page for a count the operation deliberately\n // supports.\n const cap = Math.max(MEMORY_SEARCH_CANDIDATE_CAP, target * 2);\n if (served >= cap) break;\n limit = Math.min(served * 2, cap);\n }\n return results.slice(0, target);\n}\n\n/**\n * The whole ranked memory-search path behind `POST /engram/v1/memories/search`\n * and the `memory_search` tool: pick the flat-corpus or namespace-aware\n * backend, then apply generic-recall exclusions before the caller's cap.\n */\nexport async function runScopedMemorySearch(options: {\n query: string;\n budget: number;\n /** `false` when the caller named no `maxResults`; see the helper below. */\n sendInitialLimit: boolean;\n /**\n * Authorize the scope. Runs BEFORE any budget decision so a zero budget - a\n * valid empty search - can never skip the namespace/principal gate and turn\n * an access error into a successful empty result.\n */\n authorizeScope(): Promise<void> | void;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n namespacesEnabled: boolean;\n isExcluded(memoryPath: string): boolean;\n flatCorpus(\n limit: number | undefined,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n namespaced(\n limit: number | undefined,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n}): Promise<Array<{ path: string; score: number; snippet: string }>> {\n await options.authorizeScope();\n const results = await searchWithGenericExclusion({\n budget: options.budget,\n sendInitialLimit: options.sendInitialLimit,\n isExcluded: options.isExcluded,\n search: (limit) =>\n options.namespacesEnabled ? options.namespaced(limit) : options.flatCorpus(limit),\n });\n return results.map((hit) => ({\n path: hit.path,\n score: hit.score,\n snippet: (hit.snippet ?? \"\").slice(0, 800),\n }));\n}\n\n/** Everything the ranked memory-search surface needs from the service. */\nexport interface ScopedMemorySearchDeps {\n namespacesEnabled: boolean;\n defaultBudget: number;\n isExcluded(memoryPath: string): boolean;\n /** Flat-corpus authorization; throws when the namespace is unreadable. */\n authorizeFlatCorpus(namespace: string | undefined, principal: string | undefined): void;\n /** Namespace-aware authorization; throws, else returns the search fan-out. */\n authorizeNamespaces(\n namespace: string | undefined,\n principal: string | undefined,\n collection: string | undefined,\n ): Promise<string[]>;\n searchAcrossNamespaces(params: {\n query: string;\n namespaces?: string[];\n maxResults?: number;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n }): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n searchGlobal(\n query: string,\n maxResults?: number,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n search(\n query: string,\n collection?: string,\n maxResults?: number,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n}\n\n/** The whole `memory_search` surface: validate, authorize, search, shape. */\nexport async function memorySearchThroughScope(\n deps: ScopedMemorySearchDeps,\n request: {\n query: string;\n namespace?: string;\n maxResults?: number;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n principal?: string;\n },\n): Promise<{\n query: string;\n results: Array<{ path: string; score: number; snippet: string }>;\n count: number;\n}> {\n const { query, namespace, maxResults, mode, principal } = request;\n // BOTH branches take the mode, and only the flat one reaches\n // `assertFlatCorpusOptions` — validate here so a namespaced request cannot\n // rank differently on a typo, and before any budget short-circuit.\n assertMemorySearchMode(mode);\n assertMemorySearchLimit(maxResults);\n const collection = request.collection?.trim();\n if (request.collection !== undefined && !collection) {\n throw new EngramAccessInputError(\"collection must be a non-empty string\");\n }\n let searchNamespaces: string[] = [];\n const results = await runScopedMemorySearch({\n query, collection, mode,\n namespacesEnabled: deps.namespacesEnabled,\n isExcluded: deps.isExcluded,\n budget: maxResults ?? deps.defaultBudget,\n sendInitialLimit: maxResults !== undefined,\n authorizeScope: async () => {\n if (!deps.namespacesEnabled) {\n // Validate the option combination here too: a zero budget must not be\n // a way to slip an invalid request past the check.\n assertFlatCorpusOptions(mode, collection);\n return deps.authorizeFlatCorpus(namespace, principal);\n }\n searchNamespaces = await deps.authorizeNamespaces(namespace, principal, collection);\n },\n flatCorpus: (limit) =>\n runFlatCorpusMemorySearch({\n query, maxResults: limit, collection, mode,\n searchAcrossNamespaces: (p) => deps.searchAcrossNamespaces(p),\n searchGlobal: (q, globalLimit) => deps.searchGlobal(q, globalLimit),\n search: (q, coll, searchLimit) => deps.search(q, coll, searchLimit),\n }),\n namespaced: (limit) =>\n runMemorySearchFanout({\n query, maxResults: limit, principal, collection, mode,\n requestedNamespace: namespace,\n namespaces: searchNamespaces,\n search: (p) => deps.searchAcrossNamespaces(p),\n }),\n });\n return { query, results, count: results.length };\n}\n"],"mappings":"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","names":[]}