@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,112 @@
1
+ /**
2
+ * @fileoverview Per-lane value escapers — §5.4, "the security core".
3
+ *
4
+ * A scope filter is a string template. Every dimension value that reaches it is interpolated into
5
+ * a syntax with its own quoting rules, and until now nothing escaped anything: an author was
6
+ * expected to remember. That is the wrong default in both directions — it puts the obligation on
7
+ * the person least likely to be thinking about injection, and it fails silently when forgotten.
8
+ *
9
+ * So escaping here is **automatic and keyed off the lane**, with an explicit, greppable opt-out.
10
+ * The renderer escapes every interpolated context value for the lane it is rendering into; an
11
+ * author who genuinely needs a raw value writes `contextRaw.…` instead of `context.…`, which shows
12
+ * up in a grep and in review.
13
+ *
14
+ * WHY DEFAULT-ON IS SAFE TO ADD TO AN EXISTING SYSTEM
15
+ *
16
+ * Because §5.4 also prohibits `freetext` in any filter position, a value that reaches a restricting
17
+ * filter is a uuid, enum, int, iso-date or bool. **None of those grammars can contain a character
18
+ * any of these escapers would change.** So for every legitimate value the escaper is a no-op, and
19
+ * existing templates keep rendering byte-for-byte what they rendered before. The escaper only alters
20
+ * output when a value contains a quote, a backslash or a control character — that is, when something
21
+ * has already gone wrong. That is what makes this deployable without touching a single template.
22
+ *
23
+ * WHY THE EXISTING `json` / `jsoninline` FILTERS CANNOT SERVE
24
+ *
25
+ * They escape `"` and `\` — they do not escape `'`, which is the quote character both the SQL and
26
+ * the OData lanes use. A value passed through `jsoninline` is still perfectly capable of closing a
27
+ * single-quoted SQL literal.
28
+ *
29
+ * @module @memberjunction/search-engine
30
+ */
31
+ /**
32
+ * The filter dialect a lane consumes, which determines how a value must be escaped.
33
+ *
34
+ * `none` is for positions that are NOT filters — `UserSearchString`, query transforms — where the
35
+ * value becomes search text rather than syntax, and escaping would corrupt the query rather than
36
+ * protect it.
37
+ */
38
+ export type ScopeLaneKind = 'sql' | 'odata' | 'json' | 'filter_by' | 'esdsl' | 'path' | 'none';
39
+ /**
40
+ * Escape a value for a single-quoted **T-SQL** literal.
41
+ *
42
+ * Doubling `'` is the whole of it — SQL Server has no backslash escape, so a backslash is data.
43
+ * Control characters are stripped rather than encoded: they cannot appear in any permitted value
44
+ * grammar, and leaving them in a predicate invites parser-level surprises.
45
+ */
46
+ export declare function EscapeSqlLiteral(value: string): string;
47
+ /**
48
+ * Escape a value for a single-quoted **OData** string literal (Azure AI Search `$filter`).
49
+ *
50
+ * OData v4 also escapes `'` by doubling. Azure additionally treats a backslash as an escape inside
51
+ * some function arguments, so it is doubled too — harmless for values that contain none, which is
52
+ * every legitimate value.
53
+ */
54
+ export declare function EscapeODataLiteral(value: string): string;
55
+ /**
56
+ * Escape a value for placement inside a double-quoted **JSON** string.
57
+ *
58
+ * Deliberately escapes only what JSON requires, without adding the surrounding quotes: templates
59
+ * write `"{{ context.X }}"` and supply their own. Returning a quoted string here would produce
60
+ * `""value""` in every existing template.
61
+ */
62
+ export declare function EscapeJsonValue(value: string): string;
63
+ /**
64
+ * Escape a value for a **Typesense `filter_by`** clause.
65
+ *
66
+ * Neutralises the value delimiter (backtick — Typesense has no documented escape for it, so it is
67
+ * removed) and the boolean operator sequences `&&` / `||`. Everything else is left alone.
68
+ *
69
+ * An earlier version also stripped the character class `[&|:[]()]`, which **corrupted legitimate
70
+ * values**: an `iso-date` dimension carrying a time (`2026-07-28T10:30:00Z`) came back as
71
+ * `2026-07-28T103000Z`, so the filter silently matched nothing. Found by probing the "no-op on
72
+ * legitimate values" claim rather than by review. The contract here mirrors the SQL escaper's:
73
+ * neutralise the delimiter and assume the template quoted the value, exactly as a SQL template must
74
+ * supply its own single quotes.
75
+ */
76
+ export declare function EscapeFilterByLiteral(value: string): string;
77
+ /**
78
+ * Escape a value for a **storage path segment** (`FolderPath`).
79
+ *
80
+ * Path traversal is the risk, not quoting: `..` and both separators are removed so a dimension can
81
+ * never climb out of the folder its scope confined it to. This is the one escaper that is NOT a
82
+ * no-op on an otherwise-valid value — a legitimate value simply must not contain a separator.
83
+ */
84
+ export declare function EscapePathSegment(value: string): string;
85
+ /**
86
+ * Escape one value for a lane. Non-strings are returned untouched — numbers and booleans have no
87
+ * syntax to break, and re-typing them to strings here would change how templates render them.
88
+ */
89
+ export declare function EscapeScopeValue(value: unknown, kind: ScopeLaneKind): unknown;
90
+ /**
91
+ * Recursively escape every string inside a context value for a lane.
92
+ *
93
+ * Arrays are mapped element-wise, which is what makes `{{ ids | join("','") }}` safe: each member
94
+ * is escaped before the join, so a member cannot terminate the literal and inject a clause.
95
+ */
96
+ export declare function EscapeScopeValueDeep(value: unknown, kind: ScopeLaneKind): unknown;
97
+ /**
98
+ * Map a lane's existing `IndexType` to the dialect its filter is written in.
99
+ *
100
+ * §5.5 proposed a new `MetadataFilterKind` column for this. It turned out to be unnecessary:
101
+ * `IndexType` already carries the information — every provider dispatches on it to decide whether a
102
+ * lane is even theirs — so a second column would be a redundant field to keep in sync, wrong on
103
+ * every pre-existing row until backfilled, and a new thing for authors to get wrong. Deriving is
104
+ * strictly better. If a deployment ever needs to override the derivation, THEN add the column as an
105
+ * override rather than as the source of truth.
106
+ *
107
+ * `Other` maps to `json` rather than to `none`: an unknown external index is the case we know least
108
+ * about, so it gets escaping rather than a pass. Choosing `none` there would mean the least-understood
109
+ * lane is the only unprotected one.
110
+ */
111
+ export declare function LaneKindForIndexType(indexType: string | null | undefined): ScopeLaneKind;
112
+ //# sourceMappingURL=ScopeValueEscaper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeValueEscaper.d.ts","sourceRoot":"","sources":["../../src/generic/ScopeValueEscaper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/F;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAeD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAQjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,CASxF"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @fileoverview Per-lane value escapers — §5.4, "the security core".
3
+ *
4
+ * A scope filter is a string template. Every dimension value that reaches it is interpolated into
5
+ * a syntax with its own quoting rules, and until now nothing escaped anything: an author was
6
+ * expected to remember. That is the wrong default in both directions — it puts the obligation on
7
+ * the person least likely to be thinking about injection, and it fails silently when forgotten.
8
+ *
9
+ * So escaping here is **automatic and keyed off the lane**, with an explicit, greppable opt-out.
10
+ * The renderer escapes every interpolated context value for the lane it is rendering into; an
11
+ * author who genuinely needs a raw value writes `contextRaw.…` instead of `context.…`, which shows
12
+ * up in a grep and in review.
13
+ *
14
+ * WHY DEFAULT-ON IS SAFE TO ADD TO AN EXISTING SYSTEM
15
+ *
16
+ * Because §5.4 also prohibits `freetext` in any filter position, a value that reaches a restricting
17
+ * filter is a uuid, enum, int, iso-date or bool. **None of those grammars can contain a character
18
+ * any of these escapers would change.** So for every legitimate value the escaper is a no-op, and
19
+ * existing templates keep rendering byte-for-byte what they rendered before. The escaper only alters
20
+ * output when a value contains a quote, a backslash or a control character — that is, when something
21
+ * has already gone wrong. That is what makes this deployable without touching a single template.
22
+ *
23
+ * WHY THE EXISTING `json` / `jsoninline` FILTERS CANNOT SERVE
24
+ *
25
+ * They escape `"` and `\` — they do not escape `'`, which is the quote character both the SQL and
26
+ * the OData lanes use. A value passed through `jsoninline` is still perfectly capable of closing a
27
+ * single-quoted SQL literal.
28
+ *
29
+ * @module @memberjunction/search-engine
30
+ */
31
+ /**
32
+ * Escape a value for a single-quoted **T-SQL** literal.
33
+ *
34
+ * Doubling `'` is the whole of it — SQL Server has no backslash escape, so a backslash is data.
35
+ * Control characters are stripped rather than encoded: they cannot appear in any permitted value
36
+ * grammar, and leaving them in a predicate invites parser-level surprises.
37
+ */
38
+ export function EscapeSqlLiteral(value) {
39
+ return value.replace(/'/g, "''").replace(/[\0\b\n\r\t\x1a]/g, '');
40
+ }
41
+ /**
42
+ * Escape a value for a single-quoted **OData** string literal (Azure AI Search `$filter`).
43
+ *
44
+ * OData v4 also escapes `'` by doubling. Azure additionally treats a backslash as an escape inside
45
+ * some function arguments, so it is doubled too — harmless for values that contain none, which is
46
+ * every legitimate value.
47
+ */
48
+ export function EscapeODataLiteral(value) {
49
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "''").replace(/[\0\b\n\r\t\x1a]/g, '');
50
+ }
51
+ /**
52
+ * Escape a value for placement inside a double-quoted **JSON** string.
53
+ *
54
+ * Deliberately escapes only what JSON requires, without adding the surrounding quotes: templates
55
+ * write `"{{ context.X }}"` and supply their own. Returning a quoted string here would produce
56
+ * `""value""` in every existing template.
57
+ */
58
+ export function EscapeJsonValue(value) {
59
+ // JSON.stringify handles quotes, backslashes, control chars and unicode correctly; strip the
60
+ // quotes it adds so the result drops into an author-supplied pair.
61
+ const quoted = JSON.stringify(value);
62
+ return quoted.slice(1, -1);
63
+ }
64
+ /**
65
+ * Escape a value for a **Typesense `filter_by`** clause.
66
+ *
67
+ * Neutralises the value delimiter (backtick — Typesense has no documented escape for it, so it is
68
+ * removed) and the boolean operator sequences `&&` / `||`. Everything else is left alone.
69
+ *
70
+ * An earlier version also stripped the character class `[&|:[]()]`, which **corrupted legitimate
71
+ * values**: an `iso-date` dimension carrying a time (`2026-07-28T10:30:00Z`) came back as
72
+ * `2026-07-28T103000Z`, so the filter silently matched nothing. Found by probing the "no-op on
73
+ * legitimate values" claim rather than by review. The contract here mirrors the SQL escaper's:
74
+ * neutralise the delimiter and assume the template quoted the value, exactly as a SQL template must
75
+ * supply its own single quotes.
76
+ */
77
+ export function EscapeFilterByLiteral(value) {
78
+ return value.replace(/`/g, '').replace(/&&/g, '').replace(/\|\|/g, '').replace(/[\0\b\n\r\t\x1a]/g, '');
79
+ }
80
+ /**
81
+ * Escape a value for a **storage path segment** (`FolderPath`).
82
+ *
83
+ * Path traversal is the risk, not quoting: `..` and both separators are removed so a dimension can
84
+ * never climb out of the folder its scope confined it to. This is the one escaper that is NOT a
85
+ * no-op on an otherwise-valid value — a legitimate value simply must not contain a separator.
86
+ */
87
+ export function EscapePathSegment(value) {
88
+ return value.replace(/\.\./g, '').replace(/[/\\]/g, '').replace(/[\0\b\n\r\t\x1a]/g, '');
89
+ }
90
+ /** Escaper for each lane kind. `none`/`esdsl` pass through — see below. */
91
+ const ESCAPERS = {
92
+ sql: EscapeSqlLiteral,
93
+ odata: EscapeODataLiteral,
94
+ json: EscapeJsonValue,
95
+ filter_by: EscapeFilterByLiteral,
96
+ path: EscapePathSegment,
97
+ // Elasticsearch/OpenSearch DSL is JSON, so JSON escaping is the correct rule for it.
98
+ esdsl: EscapeJsonValue,
99
+ // Not a filter position: the value becomes query text, and escaping would corrupt the search.
100
+ none: (v) => v,
101
+ };
102
+ /**
103
+ * Escape one value for a lane. Non-strings are returned untouched — numbers and booleans have no
104
+ * syntax to break, and re-typing them to strings here would change how templates render them.
105
+ */
106
+ export function EscapeScopeValue(value, kind) {
107
+ if (typeof value !== 'string')
108
+ return value;
109
+ return ESCAPERS[kind](value);
110
+ }
111
+ /**
112
+ * Recursively escape every string inside a context value for a lane.
113
+ *
114
+ * Arrays are mapped element-wise, which is what makes `{{ ids | join("','") }}` safe: each member
115
+ * is escaped before the join, so a member cannot terminate the literal and inject a clause.
116
+ */
117
+ export function EscapeScopeValueDeep(value, kind) {
118
+ if (Array.isArray(value))
119
+ return value.map((v) => EscapeScopeValueDeep(v, kind));
120
+ if (value !== null && typeof value === 'object') {
121
+ const out = {};
122
+ for (const [k, v] of Object.entries(value))
123
+ out[k] = EscapeScopeValueDeep(v, kind);
124
+ return out;
125
+ }
126
+ return EscapeScopeValue(value, kind);
127
+ }
128
+ /**
129
+ * Map a lane's existing `IndexType` to the dialect its filter is written in.
130
+ *
131
+ * §5.5 proposed a new `MetadataFilterKind` column for this. It turned out to be unnecessary:
132
+ * `IndexType` already carries the information — every provider dispatches on it to decide whether a
133
+ * lane is even theirs — so a second column would be a redundant field to keep in sync, wrong on
134
+ * every pre-existing row until backfilled, and a new thing for authors to get wrong. Deriving is
135
+ * strictly better. If a deployment ever needs to override the derivation, THEN add the column as an
136
+ * override rather than as the source of truth.
137
+ *
138
+ * `Other` maps to `json` rather than to `none`: an unknown external index is the case we know least
139
+ * about, so it gets escaping rather than a pass. Choosing `none` there would mean the least-understood
140
+ * lane is the only unprotected one.
141
+ */
142
+ export function LaneKindForIndexType(indexType) {
143
+ switch (indexType) {
144
+ case 'AzureAISearch': return 'odata';
145
+ case 'Typesense': return 'filter_by';
146
+ case 'Elasticsearch':
147
+ case 'OpenSearch': return 'esdsl';
148
+ case 'Vector': return 'json';
149
+ default: return 'json';
150
+ }
151
+ }
152
+ //# sourceMappingURL=ScopeValueEscaper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeValueEscaper.js","sourceRoot":"","sources":["../../src/generic/ScopeValueEscaper.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,21 @@
22
22
  import { IMetadataProvider, UserInfo } from '@memberjunction/core';
23
23
  import { SearchEngineBase } from '@memberjunction/core-entities';
24
24
  import { BaseSingleton } from '@memberjunction/global';
25
- import { SearchParams, SearchResult, SearchResultItem, SearchStreamEvent } from './search.types.js';
25
+ import { SearchParams, SearchResult, SearchResultItem, SearchStreamEvent, ScopePrincipals } from './search.types.js';
26
+ import { ScopeDimensionResolver } from './ScopeDimensionResolver.js';
27
+ import { ScopeExplanation, ExplainScopeInput } from './ScopeExplanation.js';
28
+ /**
29
+ * Collects lane problems keyed by the lane's **row ID** instead of throwing.
30
+ *
31
+ * Present ⇒ the caller is EXPLAINING a hypothetical search and wants every broken lane in one
32
+ * pass. Absent ⇒ the caller is RUNNING a real search and the first unusable restriction must
33
+ * abort it, because the alternative is querying that lane unfiltered.
34
+ *
35
+ * Keyed by row ID rather than by display name: two `SearchScopeEntity` rows over the SAME
36
+ * entity is a legitimate configuration (two different `ExtraFilter`s), and keying by the entity
37
+ * name made them collide — one broken lane then reported its sibling as skipped too.
38
+ */
39
+ type LaneProblemCollector = Map<string, string>;
26
40
  /**
27
41
  * Configuration options for the SearchEngine.
28
42
  */
@@ -93,10 +107,12 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
93
107
  private _defaultMaxResults;
94
108
  private _defaultOverfetchFactor;
95
109
  /**
96
- * Minimum trimmed query length we accept. One- and two-character queries against
97
- * a `LIKE '%term%'` fan-out are essentially full-database scans with negligible
110
+ * Minimum trimmed query length we accept. A single-character query against a
111
+ * `LIKE '%term%'` fan-out is essentially a full-database scan with negligible
98
112
  * relevance — the providers also enforce this, but we short-circuit here to
99
- * avoid the cache lookup and provider dispatch overhead too.
113
+ * avoid the cache lookup and provider dispatch overhead too. Set to 2 (was 3) so
114
+ * legitimate short queries aren't silently dropped (bug C3); must stay in lockstep
115
+ * with the providers' MIN_TERM_LENGTH.
100
116
  */
101
117
  private static readonly MIN_TERM_LENGTH;
102
118
  /**
@@ -108,6 +124,12 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
108
124
  /** Maximum cached entries across all users. LRU-evicted on overflow. */
109
125
  private static readonly CACHE_MAX_ENTRIES;
110
126
  private _cache;
127
+ private _dimensionResolver;
128
+ /**
129
+ * Resolver for a scope's declared Search Context dimensions. Overridable so a host can
130
+ * supply additional derivation sources (e.g. an external signal) without forking the engine.
131
+ */
132
+ protected get dimensionResolver(): ScopeDimensionResolver;
111
133
  /** Access the cached provider metadata from SearchEngineBase */
112
134
  protected get Base(): SearchEngineBase;
113
135
  /** Resolve the metadata provider via SearchEngineBase (which extends BaseEngine and tracks ProviderToUse). */
@@ -180,11 +202,50 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
180
202
  */
181
203
  streamSearch(params: SearchParams, contextUser: UserInfo): AsyncIterable<SearchStreamEvent>;
182
204
  /**
183
- * Build a stable cache key for a search. Includes the user identity so RLS
184
- * scopes never bleed across users, plus the trimmed query, MaxResults,
185
- * MinScore, and a deterministic projection of Filters.
205
+ * Deterministically serialize a value with object keys sorted, so that two
206
+ * logically-identical inputs always produce the same string.
207
+ *
208
+ * `JSON.stringify` preserves *insertion* order, which means a caller that builds
209
+ * `SecondaryScopes` by spreading (a common pattern) can emit the same dimensions in
210
+ * different orders across calls. Left unsorted that causes avoidable cache misses;
211
+ * sorted, identity is stable. Array order is PRESERVED — see buildCacheKey for why
212
+ * `ScopeIDs` order is significant.
213
+ */
214
+ protected stableStringify(value: unknown): string;
215
+ /**
216
+ * Build a stable cache key for a search.
217
+ *
218
+ * The key must include EVERY input that can change the result set, or the cache
219
+ * will serve one caller's results to another. Two of those inputs were previously
220
+ * missing and both are tenancy/authorization-relevant:
221
+ *
222
+ * - `SearchContext` — carries `PrimaryScopeRecordID` (the TENANT) and the
223
+ * `SecondaryScopes` dimensions. Omitting it meant a user with access to two
224
+ * tenants could be served the other tenant's results for up to the cache TTL,
225
+ * and that two searches differing only by dimension (channel, skill, …)
226
+ * collided. This is the reason for the fix.
227
+ * - `ScopeIDs` — determines which corpora are searched at all.
228
+ *
229
+ * Also folded in: `Mode`, `FusionWeightsOverride` and `PermissionOverfetchFactor`
230
+ * (all change ranking or the candidate pool, so they change results) and
231
+ * `AIAgentID` (conservative: agent identity participates in scope resolution and
232
+ * per-agent overrides upstream; including it can only cost a miss, never leak).
233
+ *
234
+ * `ScopeIDs` order is deliberately NOT sorted: it is behaviourally significant,
235
+ * because cross-scope reranker config and budget are taken from the first scope in
236
+ * the array that supplies one. Two different orderings can therefore produce
237
+ * different results and must not share a key.
238
+ *
239
+ * The whole projection is emitted through `stableStringify` so key-order variation
240
+ * in `SecondaryScopes` doesn't fragment the cache. The user ID stays as a readable
241
+ * prefix for debuggability.
242
+ *
243
+ * Note this runs AFTER scope resolution in `searchInternal`, so nothing here is
244
+ * circular. Entitlement is resolved by the CALLERS (`__Scoped_Search`, the
245
+ * GraphQL resolvers), which deny before reaching the engine; the engine therefore
246
+ * never caches across an allow/deny boundary.
186
247
  */
187
- private buildCacheKey;
248
+ protected buildCacheKey(trimmed: string, params: SearchParams, contextUser: UserInfo): string;
188
249
  /** Insert into the LRU cache, evicting oldest entries when over capacity. */
189
250
  private cachePut;
190
251
  /** Test / admin hook: clear the result cache. */
@@ -200,6 +261,92 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
200
261
  * @returns Search result in preview mode
201
262
  */
202
263
  PreviewSearch(query: string, maxResults: number, contextUser: UserInfo): Promise<SearchResult>;
264
+ /**
265
+ * Resolve the entire access chain for one or more scopes and report what a search WOULD be
266
+ * able to reach — **without querying any provider**.
267
+ *
268
+ * This is the answer to a question the platform previously could not answer at all: *"as
269
+ * this user, with this skill active, for this tenant — what is in bounds?"* Every input to
270
+ * that decision is transient. A grant applies because a time window is open right now; a
271
+ * dimension is discarded because it was caller-authored on a `ServerDerived` key; a lane is
272
+ * skipped because its filter lost an `{% if %}` clause. Afterwards, none of it is visible:
273
+ * a correctly-bounded result set and an accidentally-widened one look identical.
274
+ *
275
+ * Note the distinction from {@link PreviewSearch}, which is a real search capped at a few
276
+ * results. This runs **no** search — it reports the bound, not a sample of what is inside
277
+ * it. A sample cannot show you an over-broad bound, because the extra documents it would
278
+ * newly permit are exactly the ones you did not think to look for.
279
+ *
280
+ * Two properties make the output trustworthy:
281
+ *
282
+ * - It takes the **same untrusted `SearchContext` a real caller would send**, so the
283
+ * preview shows the anti-spoof discard actually happening. A dry run that only accepted
284
+ * pre-sanitized input would hide the one thing worth previewing.
285
+ * - It reports **every** broken lane in one pass rather than throwing on the first, so a
286
+ * misconfigured scope can be fixed in one sitting instead of one error per re-run.
287
+ *
288
+ * Unlike a real search this never throws for a scope-level problem; a scope that would fail
289
+ * closed comes back with `Reachable: false` and the reason, since "it would have failed"
290
+ * is precisely the finding the caller asked for.
291
+ *
292
+ * @param input scopes to explain plus the hypothetical caller context and principals
293
+ * @param contextUser the user to evaluate entitlement for
294
+ * @returns one explanation per requested scope, in the order requested
295
+ */
296
+ ExplainScope(input: ExplainScopeInput, contextUser: UserInfo): Promise<ScopeExplanation[]>;
297
+ /**
298
+ * Build the principal set a dimension's expansion query may bind.
299
+ *
300
+ * Exists so the real search path and the `ExplainScope` dry run cannot construct principals
301
+ * differently. They already did once: `ExplainScope` passed the agent and the search path
302
+ * passed nothing, so any scope deriving its bound from `AgentID` previewed one bound and
303
+ * searched with another. A single conversion site makes that class of drift unrepresentable
304
+ * rather than merely fixed.
305
+ *
306
+ * Accepts anything carrying the two principal IDs, which both `SearchParams` and
307
+ * `ExplainScopeInput` do.
308
+ */
309
+ protected principalsFrom(source: {
310
+ AIAgentID?: string | null;
311
+ AISkillID?: string | null;
312
+ }): ScopePrincipals;
313
+ /** Explain a single scope. Never throws — a failure to resolve IS the explanation. */
314
+ private explainOneScope;
315
+ /** Resolve entitlement for the dry run, including the skill and tenant principals. */
316
+ private explainEntitlement;
317
+ /** Load an agent or skill principal by ID; null when no ID was supplied. */
318
+ private loadPrincipal;
319
+ /**
320
+ * Render every lane and report which would run.
321
+ *
322
+ * Reuses `buildScopeConstraints` with a collector rather than duplicating the render logic.
323
+ * That matters more than it looks: a separate "explain" renderer would be a second
324
+ * implementation of the guard rules, free to drift from the enforcing one, and a preview
325
+ * that disagrees with what actually runs is worse than having no preview.
326
+ */
327
+ /**
328
+ * Turn already-built constraints plus a problem map into per-lane explanations.
329
+ *
330
+ * Takes the constraints rather than rebuilding them. The previous version called
331
+ * `buildScopeConstraints` itself, which meant the SEARCH path re-rendered every Nunjucks
332
+ * template a second time on every scope of every query purely to produce a log record —
333
+ * pure waste on the hottest path in the engine.
334
+ *
335
+ * Both callers still share one rendering pass, which is what keeps the dry run honest:
336
+ * a separate explain-only renderer would be a second implementation of the guard rules,
337
+ * free to drift from the enforcing one.
338
+ */
339
+ private buildLaneExplanations;
340
+ /** Render every lane for a DRY RUN, collecting problems instead of throwing on the first. */
341
+ private explainLanes;
342
+ /** Every lane, reported as skipped for one shared reason. */
343
+ private buildAllLanesSkipped;
344
+ /** Explanation for a scope that is inactive, missing, or otherwise not loadable. */
345
+ private buildUnresolvableExplanation;
346
+ /** Parse a lane's required-key contract for display; a malformed one reports as empty. */
347
+ private safeRequiredKeys;
348
+ /** Render a filter of unknown shape as a display string. */
349
+ private stringifyFilter;
203
350
  /**
204
351
  * Load `ScopeBundle`s for each requested scope ID, filtering out inactive / expired.
205
352
  * Returns an empty array when no scope IDs are supplied (caller treats as Global).
@@ -213,6 +360,36 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
213
360
  * Assemble a `ScopeConstraints` for a single scope: Nunjucks-render each template
214
361
  * field against the `SearchContext`, then hand the rendered values to providers.
215
362
  */
363
+ /**
364
+ * Fail a search CLOSED when a scope field that RESTRICTS was authored but did not render
365
+ * usably (see `CheckRenderedTemplate`).
366
+ *
367
+ * Throwing rather than dropping the offending row is deliberate. Dropping it would empty
368
+ * the row collection, and `buildScopeConstraints` collapses an empty collection to
369
+ * `undefined` — which every provider reads as "unscoped", i.e. all entities / all indexes
370
+ * with no filter. So the surgical-looking fix is the one that widens; failing the search
371
+ * is the one that doesn't. A broken restricting template is a misconfiguration and should
372
+ * be loud and actionable, never silently degraded into a wider search.
373
+ */
374
+ protected assertRestrictingTemplateRendered(source: string | null | undefined, rendered: unknown, fieldName: string, scopeLabel: string, rowLabel: string, laneID: string, collector?: LaneProblemCollector): void;
375
+ /**
376
+ * Enforce a lane's `RequiredMetadataKeys` contract (Phase E).
377
+ *
378
+ * The rendered filter must mention every key the author declared. This is the only guard
379
+ * that catches a filter which rendered *partially* — where an optional `{% if %}` clause
380
+ * disappeared because its dimension was absent or discarded, leaving a non-empty filter
381
+ * that passes every other check while restricting on strictly less than intended.
382
+ */
383
+ protected assertRequiredMetadataKeys(declaration: string | null | undefined, laneID: string, rendered: unknown, scopeLabel: string, rowLabel: string, collector?: LaneProblemCollector): void;
384
+ /**
385
+ * Route a lane problem to the right place: throw when enforcing a real search, record when
386
+ * explaining a hypothetical one.
387
+ *
388
+ * A dry run must be able to report *every* broken lane in one pass. If it threw on the first
389
+ * one, an administrator would fix a scope one error at a time, re-running after each — and
390
+ * the whole point of the preview is to see the entire picture before anything runs.
391
+ */
392
+ private reportLaneProblem;
216
393
  private buildScopeConstraints;
217
394
  /** Resolve the EntityID → EntityName via MJ Metadata (for passing to providers that key by name). */
218
395
  private lookupEntityName;
@@ -289,6 +466,8 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
289
466
  StartTime: number;
290
467
  ContextUser: UserInfo;
291
468
  AIAgentID?: string | null;
469
+ AISkillID?: string | null;
470
+ PrimaryScopeRecordID?: string | null;
292
471
  }): Promise<void>;
293
472
  /**
294
473
  * Best-effort hook (P3.2) that writes one MJSearchExecutionLog row per
@@ -307,4 +486,5 @@ export declare class SearchEngine extends BaseSingleton<SearchEngine> {
307
486
  /** Defensive JSON parse that never throws. Returns `null` on any failure. */
308
487
  private parseJson;
309
488
  }
489
+ export {};
310
490
  //# sourceMappingURL=SearchEngine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchEngine.d.ts","sourceRoot":"","sources":["../../src/generic/SearchEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchEngine.d.ts","sourceRoot":"","sources":["../../src/generic/SearchEngine.ts"],"names":[],"mappings":"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"}