@memberjunction/search-engine 5.48.0 → 5.50.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.
- package/dist/generic/EntitySearchProvider.d.ts +17 -3
- package/dist/generic/EntitySearchProvider.d.ts.map +1 -1
- package/dist/generic/EntitySearchProvider.js +23 -6
- package/dist/generic/EntitySearchProvider.js.map +1 -1
- package/dist/generic/ExternalHitMapper.d.ts +48 -0
- package/dist/generic/ExternalHitMapper.d.ts.map +1 -0
- package/dist/generic/ExternalHitMapper.js +88 -0
- package/dist/generic/ExternalHitMapper.js.map +1 -0
- package/dist/generic/FullTextSearchProvider.d.ts +9 -1
- package/dist/generic/FullTextSearchProvider.d.ts.map +1 -1
- package/dist/generic/FullTextSearchProvider.js +11 -3
- package/dist/generic/FullTextSearchProvider.js.map +1 -1
- package/dist/generic/ScopeDimensionResolver.d.ts +146 -0
- package/dist/generic/ScopeDimensionResolver.d.ts.map +1 -0
- package/dist/generic/ScopeDimensionResolver.js +464 -0
- package/dist/generic/ScopeDimensionResolver.js.map +1 -0
- package/dist/generic/ScopeExplanation.d.ts +141 -0
- package/dist/generic/ScopeExplanation.d.ts.map +1 -0
- package/dist/generic/ScopeExplanation.js +72 -0
- package/dist/generic/ScopeExplanation.js.map +1 -0
- package/dist/generic/ScopeFilterGuard.d.ts +127 -0
- package/dist/generic/ScopeFilterGuard.d.ts.map +1 -0
- package/dist/generic/ScopeFilterGuard.js +290 -0
- package/dist/generic/ScopeFilterGuard.js.map +1 -0
- package/dist/generic/ScopeTemplateRenderer.d.ts +12 -2
- package/dist/generic/ScopeTemplateRenderer.d.ts.map +1 -1
- package/dist/generic/ScopeTemplateRenderer.js +25 -5
- package/dist/generic/ScopeTemplateRenderer.js.map +1 -1
- package/dist/generic/ScopeValueEscaper.d.ts +112 -0
- package/dist/generic/ScopeValueEscaper.d.ts.map +1 -0
- package/dist/generic/ScopeValueEscaper.js +152 -0
- package/dist/generic/ScopeValueEscaper.js.map +1 -0
- package/dist/generic/SearchEngine.d.ts +188 -8
- package/dist/generic/SearchEngine.d.ts.map +1 -1
- package/dist/generic/SearchEngine.js +600 -35
- package/dist/generic/SearchEngine.js.map +1 -1
- package/dist/generic/VectorSearchProvider.d.ts +25 -1
- package/dist/generic/VectorSearchProvider.d.ts.map +1 -1
- package/dist/generic/VectorSearchProvider.js +81 -33
- package/dist/generic/VectorSearchProvider.js.map +1 -1
- package/dist/generic/search.types.d.ts +160 -0
- package/dist/generic/search.types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/permissions/SearchScopePermissionResolver.d.ts +37 -2
- package/dist/permissions/SearchScopePermissionResolver.d.ts.map +1 -1
- package/dist/permissions/SearchScopePermissionResolver.js +74 -2
- package/dist/permissions/SearchScopePermissionResolver.js.map +1 -1
- package/dist/providers/AzureAISearchProvider.d.ts.map +1 -1
- package/dist/providers/AzureAISearchProvider.js +24 -6
- package/dist/providers/AzureAISearchProvider.js.map +1 -1
- package/dist/providers/ElasticsearchSearchProvider.d.ts.map +1 -1
- package/dist/providers/ElasticsearchSearchProvider.js +17 -5
- package/dist/providers/ElasticsearchSearchProvider.js.map +1 -1
- package/dist/providers/OpenSearchSearchProvider.d.ts.map +1 -1
- package/dist/providers/OpenSearchSearchProvider.js +16 -5
- package/dist/providers/OpenSearchSearchProvider.js.map +1 -1
- package/dist/providers/TypesenseSearchProvider.d.ts.map +1 -1
- package/dist/providers/TypesenseSearchProvider.js +17 -6
- package/dist/providers/TypesenseSearchProvider.js.map +1 -1
- 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"}
|