@memberjunction/search-engine 5.49.0 → 5.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/generic/EntitySearchProvider.d.ts +17 -3
  2. package/dist/generic/EntitySearchProvider.d.ts.map +1 -1
  3. package/dist/generic/EntitySearchProvider.js +23 -6
  4. package/dist/generic/EntitySearchProvider.js.map +1 -1
  5. package/dist/generic/ExternalHitMapper.d.ts +48 -0
  6. package/dist/generic/ExternalHitMapper.d.ts.map +1 -0
  7. package/dist/generic/ExternalHitMapper.js +88 -0
  8. package/dist/generic/ExternalHitMapper.js.map +1 -0
  9. package/dist/generic/FullTextSearchProvider.d.ts +9 -1
  10. package/dist/generic/FullTextSearchProvider.d.ts.map +1 -1
  11. package/dist/generic/FullTextSearchProvider.js +11 -3
  12. package/dist/generic/FullTextSearchProvider.js.map +1 -1
  13. package/dist/generic/ScopeDimensionResolver.d.ts +146 -0
  14. package/dist/generic/ScopeDimensionResolver.d.ts.map +1 -0
  15. package/dist/generic/ScopeDimensionResolver.js +464 -0
  16. package/dist/generic/ScopeDimensionResolver.js.map +1 -0
  17. package/dist/generic/ScopeExplanation.d.ts +141 -0
  18. package/dist/generic/ScopeExplanation.d.ts.map +1 -0
  19. package/dist/generic/ScopeExplanation.js +72 -0
  20. package/dist/generic/ScopeExplanation.js.map +1 -0
  21. package/dist/generic/ScopeFilterGuard.d.ts +127 -0
  22. package/dist/generic/ScopeFilterGuard.d.ts.map +1 -0
  23. package/dist/generic/ScopeFilterGuard.js +290 -0
  24. package/dist/generic/ScopeFilterGuard.js.map +1 -0
  25. package/dist/generic/ScopeTemplateRenderer.d.ts +12 -2
  26. package/dist/generic/ScopeTemplateRenderer.d.ts.map +1 -1
  27. package/dist/generic/ScopeTemplateRenderer.js +25 -5
  28. package/dist/generic/ScopeTemplateRenderer.js.map +1 -1
  29. package/dist/generic/ScopeValueEscaper.d.ts +112 -0
  30. package/dist/generic/ScopeValueEscaper.d.ts.map +1 -0
  31. package/dist/generic/ScopeValueEscaper.js +152 -0
  32. package/dist/generic/ScopeValueEscaper.js.map +1 -0
  33. package/dist/generic/SearchEngine.d.ts +188 -8
  34. package/dist/generic/SearchEngine.d.ts.map +1 -1
  35. package/dist/generic/SearchEngine.js +600 -35
  36. package/dist/generic/SearchEngine.js.map +1 -1
  37. package/dist/generic/VectorSearchProvider.d.ts +2 -1
  38. package/dist/generic/VectorSearchProvider.d.ts.map +1 -1
  39. package/dist/generic/VectorSearchProvider.js +23 -26
  40. package/dist/generic/VectorSearchProvider.js.map +1 -1
  41. package/dist/generic/search.types.d.ts +160 -0
  42. package/dist/generic/search.types.d.ts.map +1 -1
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/permissions/SearchScopePermissionResolver.d.ts +37 -2
  48. package/dist/permissions/SearchScopePermissionResolver.d.ts.map +1 -1
  49. package/dist/permissions/SearchScopePermissionResolver.js +74 -2
  50. package/dist/permissions/SearchScopePermissionResolver.js.map +1 -1
  51. package/dist/providers/AzureAISearchProvider.d.ts.map +1 -1
  52. package/dist/providers/AzureAISearchProvider.js +24 -6
  53. package/dist/providers/AzureAISearchProvider.js.map +1 -1
  54. package/dist/providers/ElasticsearchSearchProvider.d.ts.map +1 -1
  55. package/dist/providers/ElasticsearchSearchProvider.js +17 -5
  56. package/dist/providers/ElasticsearchSearchProvider.js.map +1 -1
  57. package/dist/providers/OpenSearchSearchProvider.d.ts.map +1 -1
  58. package/dist/providers/OpenSearchSearchProvider.js +16 -5
  59. package/dist/providers/OpenSearchSearchProvider.js.map +1 -1
  60. package/dist/providers/TypesenseSearchProvider.d.ts.map +1 -1
  61. package/dist/providers/TypesenseSearchProvider.js +17 -6
  62. package/dist/providers/TypesenseSearchProvider.js.map +1 -1
  63. package/package.json +8 -8
@@ -0,0 +1,464 @@
1
+ /**
2
+ * @fileoverview Resolves a scope's declared Search Context dimensions.
3
+ *
4
+ * `SearchScope.SearchContextConfig` has always documented `dimensions[]`,
5
+ * `inheritanceMode` and `strictValidation` — but nothing read it. Dimensions therefore
6
+ * arrived as a free-form bag that **any** caller could author, including an LLM writing a
7
+ * tool call: `parseSecondaryScopes` type-cleaned the JSON and handed it straight to the
8
+ * template renderer. A value anyone in the call chain can set is a narrowing convenience,
9
+ * not an access bound, which is why apps that encoded a boundary as a dimension were doing
10
+ * something the platform could not honour.
11
+ *
12
+ * This resolver makes the declaration enforceable:
13
+ *
14
+ * - **`trust: 'ServerDerived'` ⇒ caller values for that key are DISCARDED**, and the value
15
+ * is derived by the engine (an approved `MJ: Queries` row, or a declared default). This is
16
+ * the anti-spoof rule and the reason a dimension can now carry an access decision.
17
+ * - **`valueType` is enforced.** A value failing its grammar REJECTS the search rather than
18
+ * being coerced or silently dropped.
19
+ * - **`freetext` is forbidden on a restricting dimension** — free text interpolated into a
20
+ * filter cannot be made safe by validation; it belongs in the query, not the bound.
21
+ * - **`narrowingOf` is a lattice meet**, so a caller can only ever narrow a server-derived
22
+ * dimension. Never widen.
23
+ * - **`strictValidation` has teeth** — an undeclared caller key rejects the search.
24
+ *
25
+ * Backwards compatibility is the design constraint: a scope whose `SearchContextConfig` is
26
+ * null or has no dimensions is returned **untouched**, so every existing scope behaves
27
+ * exactly as before.
28
+ *
29
+ * @module @memberjunction/search-engine
30
+ */
31
+ import { LogStatus, RunQuery } from '@memberjunction/core';
32
+ /** Thrown when resolution cannot proceed safely. Callers must fail the search closed. */
33
+ export class ScopeDimensionError extends Error {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = 'ScopeDimensionError';
37
+ }
38
+ }
39
+ const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
40
+ export class ScopeDimensionResolver {
41
+ /**
42
+ * Resolve the effective Search Context for a scope.
43
+ *
44
+ * @throws {ScopeDimensionError} when a declared dimension cannot be resolved safely —
45
+ * the caller MUST fail the search rather than proceed with a partial bound.
46
+ */
47
+ async Resolve(input) {
48
+ const diagnostics = [];
49
+ const config = this.parseConfig(input.Scope);
50
+ // No declaration ⇒ legacy behaviour, untouched. This is what keeps every existing
51
+ // scope working byte-for-byte.
52
+ if (!config || !config.dimensions?.length) {
53
+ return { Context: input.CallerContext, Diagnostics: diagnostics, Provenance: [] };
54
+ }
55
+ const callerScopes = input.CallerContext?.SecondaryScopes ?? {};
56
+ const declared = new Map(config.dimensions.map((d) => [d.name, d]));
57
+ this.rejectUndeclaredKeys(config, declared, callerScopes, input.Scope);
58
+ this.validateDeclaration(config, input.Scope);
59
+ const resolved = {};
60
+ const provenance = [];
61
+ for (const dim of this.orderByDependency(config.dimensions)) {
62
+ // §5.12 — an ADVISORY dimension fails SOFT. A boundary that cannot be resolved must
63
+ // refuse the search; an advisory one must not, because its only power is to remove
64
+ // content from an already-entitled set. Dropping it leaves a superseded corpus in the
65
+ // results, which is a relevance regression, not an access one.
66
+ const outcome = dim.advisory === true
67
+ ? await this.resolveAdvisory(dim, callerScopes, resolved, input, diagnostics)
68
+ : await this.resolveOne(dim, callerScopes, resolved, input, diagnostics);
69
+ if (outcome.Value !== undefined && outcome.Value !== null)
70
+ resolved[dim.name] = outcome.Value;
71
+ provenance.push({
72
+ Name: dim.name,
73
+ Value: outcome.Value ?? null,
74
+ Provenance: outcome.Provenance,
75
+ Restricts: dim.restricts === true,
76
+ // Effective mode, not the raw declaration: a boundary with no explicit mode is
77
+ // strict, and an auditor should see what was ENFORCED rather than what was written.
78
+ InheritanceMode: dim.inheritanceMode ?? (dim.restricts === true ? 'strict' : 'cascading'),
79
+ Note: outcome.Note,
80
+ });
81
+ }
82
+ return {
83
+ Context: {
84
+ PrimaryScopeEntityID: input.CallerContext?.PrimaryScopeEntityID,
85
+ PrimaryScopeRecordID: input.CallerContext?.PrimaryScopeRecordID,
86
+ SecondaryScopes: Object.keys(resolved).length ? resolved : undefined,
87
+ },
88
+ Diagnostics: diagnostics,
89
+ Provenance: provenance,
90
+ };
91
+ }
92
+ /** Parse the scope's declaration; a malformed config fails closed rather than being ignored. */
93
+ parseConfig(scope) {
94
+ const raw = scope.SearchContextConfig;
95
+ if (!raw || !raw.trim())
96
+ return null;
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ if (!parsed || typeof parsed !== 'object')
100
+ throw new Error('not an object');
101
+ return parsed;
102
+ }
103
+ catch (e) {
104
+ const msg = e instanceof Error ? e.message : String(e);
105
+ throw new ScopeDimensionError(`SearchScope "${scope.Name}" has a SearchContextConfig that is not valid JSON (${msg}). ` +
106
+ `Refusing to search: the scope declares dimensions but they cannot be read, so no bound can be enforced.`);
107
+ }
108
+ }
109
+ /**
110
+ * Validate the DECLARATION itself, before any dimension is dispatched.
111
+ *
112
+ * This has to run here rather than inside per-dimension resolution. `advisory` routes to a
113
+ * fail-soft path that deliberately swallows errors, so a contradiction checked *inside* that path
114
+ * would be swallowed too — and the failure mode was the worst available one: a dimension
115
+ * declaring `restricts: true` alongside `advisory: true` was silently given the advisory
116
+ * posture, i.e. a declared boundary quietly became droppable. Found by a test, not by review.
117
+ */
118
+ validateDeclaration(config, scope) {
119
+ for (const dim of config.dimensions) {
120
+ if (dim.restricts === true && dim.advisory === true) {
121
+ throw new ScopeDimensionError(`SearchScope "${scope.Name}" dimension "${dim.name}" declares BOTH restricts:true and ` +
122
+ `advisory:true. A boundary must fail closed and may never be dropped; an advisory ` +
123
+ `dimension fails soft and may only subtract. Pick one — if it carries an access ` +
124
+ `decision it is not advisory.`);
125
+ }
126
+ // §5.9 — a boundary defaults to STRICT; choosing the permissive mode must be explicit.
127
+ // `inheritanceMode` was the exact failure this resolver exists to fix, reproduced: the
128
+ // field shipped in the declaration type and NOTHING read it, so `cascading` on a
129
+ // boundary looked configured while behaving as an unenforced comment.
130
+ if (dim.restricts === true && dim.inheritanceMode === 'cascading'
131
+ && dim.acknowledgeCascadingOnBoundary !== true) {
132
+ throw new ScopeDimensionError(`Dimension "${dim.name}" is restricting and declares inheritanceMode 'cascading', which is ` +
133
+ `PERMISSIVE — content lacking the dimension would apply to everyone, widening the bound. ` +
134
+ `If that is genuinely intended, set "acknowledgeCascadingOnBoundary": true on the dimension ` +
135
+ `so the choice is deliberate and greppable. Otherwise use 'strict' (the default for a boundary).`);
136
+ }
137
+ if (dim.restricts === true && dim.valueType === 'freetext') {
138
+ throw new ScopeDimensionError(`Dimension "${dim.name}" is restricting but declares valueType 'freetext'. Free text cannot ` +
139
+ `be made safe inside a filter — model it as a query input instead.`);
140
+ }
141
+ if (dim.supersededByRules?.length && dim.advisory !== true) {
142
+ throw new ScopeDimensionError(`SearchScope "${scope.Name}" dimension "${dim.name}" declares supersededByRules but is not ` +
143
+ `advisory:true. Supersession rules may only ever REMOVE content; allowing them on a ` +
144
+ `non-advisory dimension would let an ordered rule participate in the bound.`);
145
+ }
146
+ for (const rule of dim.supersededByRules ?? []) {
147
+ if (!rule.key || !Object.keys(rule.when ?? {}).length) {
148
+ throw new ScopeDimensionError(`SearchScope "${scope.Name}" dimension "${dim.name}" has a supersession rule missing ` +
149
+ `"key" or "when". A rule with no conditions would fire unconditionally.`);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ /** `strictValidation` with teeth — previously this only warned and used the value anyway. */
155
+ rejectUndeclaredKeys(config, declared, callerScopes, scope) {
156
+ if (!config.strictValidation)
157
+ return;
158
+ const undeclared = Object.keys(callerScopes).filter((k) => !declared.has(k));
159
+ if (undeclared.length) {
160
+ throw new ScopeDimensionError(`SearchScope "${scope.Name}" has strictValidation enabled and the caller supplied undeclared ` +
161
+ `dimension(s): ${undeclared.join(', ')}. Refusing to search.`);
162
+ }
163
+ }
164
+ /** Resolve `narrowingOf` targets before their dependents; otherwise declaration order. */
165
+ orderByDependency(dimensions) {
166
+ const byName = new Map(dimensions.map((d) => [d.name, d]));
167
+ const out = [];
168
+ const seen = new Set();
169
+ const visit = (d, chain) => {
170
+ if (seen.has(d.name))
171
+ return;
172
+ if (chain.has(d.name)) {
173
+ throw new ScopeDimensionError(`Dimension "${d.name}" has a circular narrowingOf chain.`);
174
+ }
175
+ if (d.narrowingOf) {
176
+ const target = byName.get(d.narrowingOf);
177
+ if (!target) {
178
+ throw new ScopeDimensionError(`Dimension "${d.name}" declares narrowingOf "${d.narrowingOf}", which is not a declared dimension.`);
179
+ }
180
+ visit(target, new Set([...chain, d.name]));
181
+ }
182
+ seen.add(d.name);
183
+ out.push(d);
184
+ };
185
+ for (const d of dimensions)
186
+ visit(d, new Set());
187
+ // Advisory dimensions resolve LAST: their ordered rules read the values other dimensions
188
+ // produced, so they cannot be evaluated until those exist.
189
+ return [...out.filter((d) => d.advisory !== true), ...out.filter((d) => d.advisory === true)];
190
+ }
191
+ /**
192
+ * Resolve an advisory (§5.12 supersession) dimension. Never throws.
193
+ *
194
+ * Every failure path here degrades to "no supersession" rather than to a refused search. That
195
+ * asymmetry with {@link resolveOne} is the entire point of the section: entitlement composes by
196
+ * intersection and fails closed; supersession only subtracts and fails soft.
197
+ */
198
+ async resolveAdvisory(dim, callerScopes, alreadyResolved, input, diagnostics) {
199
+ if (dim.supersededByRules?.length) {
200
+ const hit = this.firstMatchingRule(dim.supersededByRules, alreadyResolved);
201
+ if (!hit) {
202
+ diagnostics.push(`no supersession rule matched for advisory dimension "${dim.name}" — nothing superseded`);
203
+ return { Value: undefined, Provenance: 'Absent' };
204
+ }
205
+ diagnostics.push(`supersession rule matched for "${dim.name}" -> "${hit.key}"${hit.because ? ` (${hit.because})` : ''}`);
206
+ return {
207
+ Value: hit.key,
208
+ Provenance: 'RuleDerived',
209
+ Note: hit.because ?? `matched rule when=${JSON.stringify(hit.when)}`,
210
+ };
211
+ }
212
+ // No rules declared: fall back to the ordinary paths, but swallow any failure.
213
+ try {
214
+ return await this.resolveOne(dim, callerScopes, alreadyResolved, input, diagnostics);
215
+ }
216
+ catch (e) {
217
+ const msg = e instanceof Error ? e.message : String(e);
218
+ diagnostics.push(`advisory dimension "${dim.name}" could not be resolved and was DROPPED (fail-soft): ${msg}`);
219
+ LogStatus(`ScopeDimensionResolver: advisory dimension "${dim.name}" dropped — ${msg}`);
220
+ return { Value: undefined, Provenance: 'Absent', Note: `dropped fail-soft: ${msg}` };
221
+ }
222
+ }
223
+ /**
224
+ * First rule whose every `when` entry matches. Order in the array IS the precedence order.
225
+ *
226
+ * A set-valued resolved dimension matches by MEMBERSHIP, so an author writes
227
+ * `when: { ActiveSkillIDs: '<exam-writer>' }` without needing to know whether the dimension is
228
+ * scalar or a set. Comparison is case-insensitive: MJ UUID casing is inconsistent, and a rule
229
+ * that never fires because of letter case would be effectively undebuggable.
230
+ */
231
+ firstMatchingRule(rules, resolved) {
232
+ const same = (a, b) => typeof a === 'string' && typeof b === 'string'
233
+ ? a.toLowerCase() === b.toLowerCase()
234
+ : a === b;
235
+ return rules.find((rule) => Object.entries(rule.when).every(([name, expected]) => {
236
+ const actual = resolved[name];
237
+ if (actual === undefined)
238
+ return false;
239
+ if (Array.isArray(actual)) {
240
+ return Array.isArray(expected)
241
+ ? expected.every((e) => actual.some((a) => same(a, e)))
242
+ : actual.some((a) => same(a, expected));
243
+ }
244
+ return same(actual, expected);
245
+ }));
246
+ }
247
+ /** Resolve a single dimension, enforcing trust, grammar, narrowing and required-ness. */
248
+ async resolveOne(dim, callerScopes, alreadyResolved, input, diagnostics) {
249
+ const restricts = dim.restricts === true;
250
+ // `restricts` is a profile: it forces the safe posture rather than relying on an
251
+ // author remembering to set each field.
252
+ const trust = restricts ? 'ServerDerived' : (dim.trust ?? 'CallerSupplied');
253
+ let { Value: value, Provenance: provenance, Note: note } = await this.resolveByTrust(dim, trust, callerScopes, input, diagnostics);
254
+ ({ Value: value, Provenance: provenance } =
255
+ this.applyDefaultValue(dim, restricts, value, provenance, diagnostics));
256
+ if (value === undefined && dim.required) {
257
+ throw new ScopeDimensionError(`Required dimension "${dim.name}" could not be resolved for scope "${input.Scope.Name}".`);
258
+ }
259
+ if (value !== undefined && dim.narrowingOf) {
260
+ const bound = alreadyResolved[dim.narrowingOf];
261
+ value = this.meet(dim, value, bound);
262
+ if (bound !== undefined) {
263
+ provenance = 'Narrowed';
264
+ note = `narrowed within "${dim.narrowingOf}"${note ? `; ${note}` : ''}`;
265
+ }
266
+ }
267
+ // An unresolved dimension is 'Absent' — UNLESS a caller value was discarded getting
268
+ // here. The discard must survive: overwriting it would log a spoof attempt as a routine
269
+ // "dimension not supplied", which is precisely the record an investigator needs and the
270
+ // one an attacker would most like erased.
271
+ if (value === undefined && provenance !== 'DiscardedCaller')
272
+ provenance = 'Absent';
273
+ return { Value: value, Provenance: provenance, Note: note };
274
+ }
275
+ /**
276
+ * Produce a value according to the dimension's TRUST, which is the whole security hinge.
277
+ *
278
+ * `ServerDerived` never merges a caller value — it discards it and derives its own. The discard
279
+ * also wins the provenance label even when a server value replaced it, because an audit needs to
280
+ * see that someone tried; a successful override would otherwise look identical to a quiet run.
281
+ */
282
+ async resolveByTrust(dim, trust, callerScopes, input, diagnostics) {
283
+ if (trust !== 'ServerDerived') {
284
+ const raw = callerScopes[dim.name];
285
+ return {
286
+ Value: raw === undefined ? undefined : this.validateGrammar(dim, raw),
287
+ Provenance: raw === undefined ? 'Absent' : 'CallerSupplied',
288
+ };
289
+ }
290
+ let note;
291
+ if (dim.name in callerScopes) {
292
+ // THE ANTI-SPOOF RULE. Discarded, never merged.
293
+ const msg = `discarded caller-supplied value for ServerDerived dimension "${dim.name}"`;
294
+ diagnostics.push(msg);
295
+ LogStatus(`ScopeDimensionResolver: ${msg}`);
296
+ note = `a caller-supplied value was discarded (${JSON.stringify(callerScopes[dim.name]).substring(0, 80)})`;
297
+ }
298
+ return {
299
+ Value: await this.deriveServerValue(dim, input),
300
+ Provenance: note ? 'DiscardedCaller' : 'ServerDerived',
301
+ Note: note,
302
+ };
303
+ }
304
+ /**
305
+ * Apply a declared `defaultValue` when nothing resolved.
306
+ *
307
+ * A default may NOT stand in for a restricting dimension: for a bound, "absent" has to mean
308
+ * deny. Allowing a default there would let an author turn a failed derivation into a silent
309
+ * grant, which is the inverse of what the bound is for.
310
+ */
311
+ applyDefaultValue(dim, restricts, value, provenance, diagnostics) {
312
+ if (value !== undefined || dim.defaultValue === undefined || dim.defaultValue === null) {
313
+ return { Value: value, Provenance: provenance };
314
+ }
315
+ if (restricts) {
316
+ throw new ScopeDimensionError(`Dimension "${dim.name}" is restricting and could not be derived; a defaultValue must not ` +
317
+ `substitute for an access bound.`);
318
+ }
319
+ diagnostics.push(`applied defaultValue for "${dim.name}"`);
320
+ // A discard must not be relabelled. `DiscardedCaller` is the security-relevant event, and a
321
+ // machine reading SearchExecutionLog.ScopeDecisionJSON finds spoof attempts by filtering on
322
+ // that exact value — so overwriting it with 'Default' hides the attempt from the only query
323
+ // anyone would run to look for it. (The Note and Diagnostics did survive, but a label a tool
324
+ // can filter on is the point.) Same failure this resolver already had once, where an
325
+ // "unresolved => Absent" fallback erased the discard.
326
+ return {
327
+ Value: dim.defaultValue,
328
+ Provenance: provenance === 'DiscardedCaller' ? 'DiscardedCaller' : 'Default',
329
+ };
330
+ }
331
+ /** Derive a ServerDerived value: an approved `MJ: Queries` row, else nothing. */
332
+ async deriveServerValue(dim, input) {
333
+ if (!dim.expansionQueryID)
334
+ return undefined;
335
+ const result = await new RunQuery().RunQuery({
336
+ QueryID: dim.expansionQueryID,
337
+ Parameters: {
338
+ PrimaryScopeRecordID: input.CallerContext?.PrimaryScopeRecordID ?? null,
339
+ UserID: input.ContextUser?.ID ?? null,
340
+ AgentID: input.Principals?.AgentID ?? null,
341
+ // A skill is a principal in the same sense an agent is (Phase D), so a scope can
342
+ // derive a different bound depending on which skill is active — the mechanism
343
+ // behind "invoking this skill changes the content".
344
+ SkillID: input.Principals?.SkillID ?? null,
345
+ },
346
+ }, input.ContextUser);
347
+ if (!result?.Success) {
348
+ throw new ScopeDimensionError(`Expansion query for dimension "${dim.name}" failed: ${result?.ErrorMessage ?? 'unknown error'}. ` +
349
+ `Refusing to search rather than proceeding without the bound.`);
350
+ }
351
+ const rows = (result.Results ?? []);
352
+ // Single-column projection convention: first column of each row.
353
+ const values = rows
354
+ .map((r) => { const v = Object.values(r)[0]; return typeof v === 'string' ? v : String(v ?? ''); })
355
+ .filter((v) => v.length > 0);
356
+ return dim.valueType === 'uuid' ? values[0] : values;
357
+ }
358
+ /**
359
+ * Meet a caller value against a SET-valued server bound.
360
+ *
361
+ * A scalar caller value is a MEMBERSHIP test — "pick one of the allowed values" — not equality
362
+ * against the set. An earlier version compared the scalar to a stringified array and therefore
363
+ * rejected every legitimate pick, which is why the two shapes are handled apart here.
364
+ */
365
+ meetAgainstSet(dim, callerValue, serverSet, callerIsSet, widened) {
366
+ const lower = (v) => String(v).toLowerCase();
367
+ const allowed = new Set(serverSet.map(lower));
368
+ if (!callerIsSet) {
369
+ if (!allowed.has(lower(callerValue))) {
370
+ throw widened(`'${String(callerValue)}' is not one of the ${allowed.size} allowed value(s)`);
371
+ }
372
+ return callerValue;
373
+ }
374
+ const intersection = callerValue.filter((v) => allowed.has(lower(v)));
375
+ if (!intersection.length) {
376
+ // Bottom. Never fall back to the unrestricted bound: an empty value renders as a
377
+ // REMOVED clause under the `{% if %}` idiom, which inverts narrowing into widening.
378
+ throw new ScopeDimensionError(`Dimension "${dim.name}" narrowed to NOTHING — the caller's values lie entirely outside the ` +
379
+ `server-derived bound "${dim.narrowingOf}". Refusing to search (never widen on a degenerate scope).`);
380
+ }
381
+ return intersection;
382
+ }
383
+ /** Enforce the declared grammar. A failure REJECTS — never coerce, never drop-and-continue. */
384
+ validateGrammar(dim, raw) {
385
+ const fail = (why) => {
386
+ throw new ScopeDimensionError(`Dimension "${dim.name}" failed its declared valueType '${dim.valueType}': ${why}`);
387
+ };
388
+ switch (dim.valueType) {
389
+ case 'uuid':
390
+ if (typeof raw !== 'string' || !UUID_RE.test(raw))
391
+ return fail(`not a uuid (${String(raw).substring(0, 40)})`);
392
+ return raw;
393
+ case 'uuid[]': {
394
+ const arr = Array.isArray(raw) ? raw : fail('not an array');
395
+ for (const v of arr)
396
+ if (!UUID_RE.test(v))
397
+ return fail(`array member is not a uuid (${String(v).substring(0, 40)})`);
398
+ return arr;
399
+ }
400
+ case 'enum':
401
+ if (typeof raw !== 'string')
402
+ return fail('not a string');
403
+ if (!dim.enumValues?.length)
404
+ return fail('no enumValues declared');
405
+ if (!dim.enumValues.includes(raw))
406
+ return fail(`'${raw}' is not one of ${dim.enumValues.join('|')}`);
407
+ return raw;
408
+ case 'int':
409
+ if (typeof raw !== 'number' || !Number.isInteger(raw))
410
+ return fail('not an integer');
411
+ return raw;
412
+ case 'iso-date': {
413
+ if (typeof raw !== 'string' || Number.isNaN(Date.parse(raw)))
414
+ return fail('not an ISO date');
415
+ return raw;
416
+ }
417
+ case 'bool':
418
+ if (typeof raw !== 'boolean')
419
+ return fail('not a boolean');
420
+ return raw;
421
+ case 'freetext':
422
+ case undefined:
423
+ default:
424
+ return raw;
425
+ }
426
+ }
427
+ /**
428
+ * Lattice meet — a caller may only NARROW. `set` intersects; `scalar` must match exactly;
429
+ * `opaque` forbids narrowing entirely.
430
+ */
431
+ meet(dim, callerValue, serverValue) {
432
+ if (serverValue === undefined)
433
+ return callerValue;
434
+ if (dim.valueDomain === 'opaque') {
435
+ throw new ScopeDimensionError(`Dimension "${dim.name}" has valueDomain 'opaque'; narrowingOf is not permitted.`);
436
+ }
437
+ // The lattice is chosen by the SHAPES of both sides, not by one declared domain. The
438
+ // important case — and the one an earlier version got wrong — is a SCALAR caller value
439
+ // narrowing a SET-valued server bound: that is a membership test ("pick one of the
440
+ // allowed values"), not an equality test. Comparing a scalar against a stringified
441
+ // array rejects every legitimate pick.
442
+ const serverIsSet = Array.isArray(serverValue);
443
+ const callerIsSet = Array.isArray(callerValue);
444
+ const lower = (v) => String(v).toLowerCase();
445
+ const widened = (detail) => new ScopeDimensionError(`Dimension "${dim.name}" would WIDEN rather than narrow the server-derived bound ` +
446
+ `"${dim.narrowingOf}": ${detail}. Refusing to search.`);
447
+ if (serverIsSet) {
448
+ return this.meetAgainstSet(dim, callerValue, serverValue, callerIsSet, widened);
449
+ }
450
+ // Server bound is a single value: the caller may only restate it.
451
+ if (callerIsSet) {
452
+ const distinct = new Set(callerValue.map(lower));
453
+ if (distinct.size !== 1 || !distinct.has(lower(serverValue))) {
454
+ throw widened(`a set that is not exactly ['${String(serverValue)}']`);
455
+ }
456
+ return serverValue;
457
+ }
458
+ if (lower(callerValue) !== lower(serverValue)) {
459
+ throw widened(`'${String(callerValue)}' != '${String(serverValue)}'`);
460
+ }
461
+ return serverValue;
462
+ }
463
+ }
464
+ //# sourceMappingURL=ScopeDimensionResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeDimensionResolver.js","sourceRoot":"","sources":["../../src/generic/ScopeDimensionResolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @fileoverview The shape of a scope decision — what a search WOULD be able to reach, and why.
3
+ *
4
+ * Everything the dimensional-scope work added is a decision made from inputs that are gone by
5
+ * the time anyone asks about them. A grant applied because a time window happened to be open;
6
+ * a dimension was discarded because it was caller-authored on a ServerDerived key; a lane was
7
+ * skipped because its filter lost a clause. Each of those is invisible afterwards — the result
8
+ * set looks the same whether it was correctly bounded or accidentally widened, so neither an
9
+ * administrator configuring a scope nor an auditor reconstructing an incident can tell.
10
+ *
11
+ * `ScopeExplanation` is that decision, captured. It is used in two places deliberately:
12
+ *
13
+ * 1. **Before** a search — `SearchEngine.ExplainScope()` runs the entire resolution chain
14
+ * (entitlement → dimensions → lane filters) and returns this WITHOUT querying anything.
15
+ * "As this user, on this date, with this skill active: what could this reach?"
16
+ * 2. **After** a search — the same object is serialized into
17
+ * `SearchExecutionLog.ScopeDecisionJSON`.
18
+ *
19
+ * One shape for both is the point. The preview an administrator approves is structurally the
20
+ * record the audit log keeps, so a claim made at configuration time is checkable against what
21
+ * actually ran, rather than being two representations that can quietly disagree.
22
+ *
23
+ * @module @memberjunction/search-engine
24
+ */
25
+ import type { DimensionExplanation, SearchContext } from './search.types.js';
26
+ import type { SearchScopePermissionLevel, SearchScopePermissionSource } from '../permissions/SearchScopePermissionResolver.js';
27
+ /** Which kind of retrieval lane a {@link LaneExplanation} describes. */
28
+ export type LaneKind = 'ExternalIndex' | 'Entity' | 'StorageAccount';
29
+ /**
30
+ * What happened to one retrieval lane.
31
+ *
32
+ * `Skipped` is the interesting state and the reason this type exists. A lane is skipped when
33
+ * its restriction could not be applied safely — an unparseable filter, a template that lost a
34
+ * clause, a required metadata key the rendered filter never mentions. Skipping is the correct
35
+ * outcome (the alternative is querying unfiltered), but it is also silent: the search still
36
+ * succeeds, just against fewer sources. Without this record, "the filter broke and we searched
37
+ * three lanes instead of four" is indistinguishable from a scope that only ever had three.
38
+ */
39
+ export interface LaneExplanation {
40
+ Kind: LaneKind;
41
+ /** Index name, entity name, or storage account ID — whatever identifies the lane to a human. */
42
+ Target: string;
43
+ /** The row ID of the lane's child record, for pinpointing which configuration to fix. */
44
+ LaneID: string;
45
+ Status: 'Active' | 'Skipped';
46
+ /** The filter as rendered for this search, or null when the lane carries no filter. */
47
+ RenderedFilter: string | null;
48
+ /** Metadata keys this lane's `RequiredMetadataKeys` contract demands, if any. */
49
+ RequiredMetadataKeys?: string[];
50
+ /** Why the lane was skipped. Absent when `Status` is `Active`. */
51
+ Reason?: string;
52
+ }
53
+ /** The entitlement half of the decision: was this principal allowed to reach the scope at all. */
54
+ export interface EntitlementExplanation {
55
+ Allowed: boolean;
56
+ Level: SearchScopePermissionLevel;
57
+ /** Which resolution path produced the answer (direct grant, role, agent fallback, skill…). */
58
+ Source: SearchScopePermissionSource;
59
+ Reason: string;
60
+ /** The principals in play, so a log row is self-describing without joining three tables. */
61
+ Principals: {
62
+ UserID: string | null;
63
+ AgentID: string | null;
64
+ SkillID: string | null;
65
+ /** Tenant the search ran for (`SearchContext.PrimaryScopeRecordID`), null if untenanted. */
66
+ PrimaryScopeRecordID: string | null;
67
+ };
68
+ }
69
+ /**
70
+ * The complete decision for one scope: whether it was reachable, how each dimension of its
71
+ * bound was decided, and what each lane ended up doing.
72
+ */
73
+ export interface ScopeExplanation {
74
+ ScopeID: string;
75
+ ScopeName: string;
76
+ /**
77
+ * The entitlement decision, or `null` when this layer did not evaluate one.
78
+ *
79
+ * `null` is a real and common state, not a placeholder: `SearchEngine` does not gate scopes
80
+ * on `SearchScopePermission` itself — the caller that selects which scopes to search does
81
+ * (the agent RAG layer, an application action). So an explanation captured *during* a search
82
+ * legitimately has no entitlement to report, while one produced by `ExplainScope()` always
83
+ * does, because the dry run resolves it on purpose.
84
+ *
85
+ * It is nullable rather than defaulted because the two plausible defaults are both wrong:
86
+ * a fabricated "allowed" makes an unevaluated search look authorized in an audit log, and a
87
+ * fabricated "denied" makes a perfectly normal search look blocked. Neither is worth the
88
+ * convenience of a non-null field.
89
+ */
90
+ Entitlement: EntitlementExplanation | null;
91
+ /** Every declared dimension with its resolved value and provenance. Empty for an undeclared scope. */
92
+ Dimensions: DimensionExplanation[];
93
+ Lanes: LaneExplanation[];
94
+ /** Free-text notes from resolution (discards, applied defaults, expansion-query detail). */
95
+ Diagnostics: string[];
96
+ /**
97
+ * True when this scope would actually contribute results: entitlement did not deny it AND
98
+ * at least one lane is active. When `Entitlement` is null, this reflects the lanes alone.
99
+ *
100
+ * Entitled-but-zero-active-lanes is the case worth surfacing on its own. It looks like a
101
+ * permissions problem to whoever reports it ("I have access but get nothing"), while the
102
+ * actual cause is every lane failing its filter guard — a configuration bug that an
103
+ * entitlement check alone will never reveal.
104
+ */
105
+ Reachable: boolean;
106
+ /**
107
+ * True when this scope configures **no lanes at all**, which in MJ means UNSCOPED — every
108
+ * provider reads an empty child configuration as "all entities, all indexes, no filter".
109
+ *
110
+ * Surfaced as its own flag because it is the finding a reviewer is most likely to be
111
+ * hunting for and least likely to spot: such a scope has no filter to inspect, so it looks
112
+ * innocuous in every other field. It is also the exact opposite of what an empty `Lanes`
113
+ * array intuitively suggests.
114
+ */
115
+ Unbounded: boolean;
116
+ /** The effective context after resolution — what the lane templates were rendered against. */
117
+ ResolvedContext?: SearchContext;
118
+ }
119
+ /** Input to {@link ScopeExplanation}-producing dry runs. */
120
+ export interface ExplainScopeInput {
121
+ /** Scopes to explain. */
122
+ ScopeIDs: string[];
123
+ /**
124
+ * The context a real caller would pass. Explaining with the SAME untrusted input a caller
125
+ * would send is the point — a preview that only accepts already-sanitized values cannot
126
+ * show you the discard, which is usually the thing you wanted to see.
127
+ */
128
+ SearchContext?: SearchContext;
129
+ /** Agent principal, if the search would run on an agent's behalf. */
130
+ AIAgentID?: string | null;
131
+ /** Skill principal, if a skill would be active. */
132
+ AISkillID?: string | null;
133
+ }
134
+ /**
135
+ * Render an explanation as human-readable lines.
136
+ *
137
+ * Kept as a pure function next to the type rather than inside the engine so a CLI, an
138
+ * Explorer panel, or a test can format one without constructing a `SearchEngine`.
139
+ */
140
+ export declare function SummarizeExplanation(explanation: ScopeExplanation): string[];
141
+ //# sourceMappingURL=ScopeExplanation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeExplanation.d.ts","sourceRoot":"","sources":["../../src/generic/ScopeExplanation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,KAAK,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAE5H,wEAAwE;AACxE,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG,QAAQ,GAAG,gBAAgB,CAAC;AAErE;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,gGAAgG;IAChG,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,uFAAuF;IACvF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kGAAkG;AAClG,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,0BAA0B,CAAC;IAClC,8FAA8F;IAC9F,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,UAAU,EAAE;QACR,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,4FAA4F;QAC5F,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;KACvC,CAAC;CACL;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,WAAW,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC3C,sGAAsG;IACtG,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,4FAA4F;IAC5F,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;;OAQG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,8FAA8F;IAC9F,eAAe,CAAC,EAAE,aAAa,CAAC;CACnC;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAC9B,yBAAyB;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,EAAE,CA0C5E"}