@atlasauth/pca-analyzer 0.0.0-stage → 0.2.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.
@@ -0,0 +1,375 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_MAX_COMBINATIONS = void 0;
4
+ exports.normalizePolicy = normalizePolicy;
5
+ exports.toModel = toModel;
6
+ exports.collectProbes = collectProbes;
7
+ exports.productSize = productSize;
8
+ exports.representatives = representatives;
9
+ exports.admits = admits;
10
+ exports.admitsAction = admitsAction;
11
+ exports.repToAction = repToAction;
12
+ exports.search = search;
13
+ const pca_1 = require("@atlasauth/pca");
14
+ const caveats_1 = require("./caveats");
15
+ const field_1 = require("./field");
16
+ const types_1 = require("./types");
17
+ /**
18
+ * The bounded decision engine. It builds a finite set of representative actions ("the grid") from the
19
+ * literals, thresholds, prefixes, verbs and resource matchers that appear in the policies (and, for
20
+ * intent conformance, the intent). Because PCA's `where` theory is a boolean combination of
21
+ * threshold/membership/prefix tests, each policy's admitted region is a union of grid cells, so one
22
+ * representative per cell is DISTINGUISHING: any two policies that differ on some action also differ on
23
+ * some representative. Evaluating the REAL `evaluatePredicates` at each representative (plus the static
24
+ * caveat gate) therefore decides reachability, subsumption, disjointness and equivalence EXACTLY for
25
+ * the decided fragment, and yields a concrete counterexample whenever a relation fails.
26
+ *
27
+ * Out-of-fragment features (`re:` regex resources, cross-field `ref` conditions) and grids larger than
28
+ * the bound are detected and reported as `approximate`, and every verdict then fails safe.
29
+ */
30
+ const ABSENT = Symbol('analyzer-absent-field');
31
+ const FRESH_VERB = '\u0000atlas-analyzer::fresh-verb\u0000';
32
+ const FRESH_RESOURCE = '\u0000atlas-analyzer::fresh-resource\u0000';
33
+ /** Default cap on the representative grid size (product of all dimensions). */
34
+ exports.DEFAULT_MAX_COMBINATIONS = 200_000;
35
+ function normalizePolicy(p) {
36
+ const predicates = Array.isArray(p.predicates) ? p.predicates : [];
37
+ const caveats = 'caveats' in p && Array.isArray(p.caveats) ? p.caveats : [];
38
+ return { predicates, caveats };
39
+ }
40
+ /**
41
+ * Collect over-approximation reasons from one condition tree. Boolean groupings (all_of/any_of/not)
42
+ * are DECIDED EXACTLY as long as every leaf uses a decidable op — the admitted region stays a boolean
43
+ * combination of the same per-field cells, so the representative grid remains distinguishing — so the
44
+ * grouping itself is not undecidable; only its hard leaves are. The genuinely-hard leaf ops
45
+ * (`like`/`matches`/`is_a`/`member_of`), a cross-field `ref`, and a whole-`action.params` condition
46
+ * force a conservative over-approximation (verdict fails safe).
47
+ */
48
+ function conditionUndecidable(c, reasons) {
49
+ if (c === null || typeof c !== 'object')
50
+ return;
51
+ if ((0, pca_1.isGroupCondition)(c)) {
52
+ if ('all_of' in c && Array.isArray(c.all_of))
53
+ for (const x of c.all_of)
54
+ conditionUndecidable(x, reasons);
55
+ else if ('any_of' in c && Array.isArray(c.any_of))
56
+ for (const x of c.any_of)
57
+ conditionUndecidable(x, reasons);
58
+ else if ('not' in c)
59
+ conditionUndecidable(c.not, reasons);
60
+ return;
61
+ }
62
+ if (!(0, pca_1.isLeafCondition)(c))
63
+ return;
64
+ if (c.ref !== undefined) {
65
+ reasons.add('cross-field `ref` condition — the relation between two fields is not resolved');
66
+ }
67
+ if (c.field === 'action.params') {
68
+ reasons.add('condition on the whole `action.params` object is not enumerated precisely');
69
+ }
70
+ if (c.op === 'like' || c.op === 'matches' || c.op === 'is_a' || c.op === 'member_of') {
71
+ reasons.add(`\`${c.op}\` condition — matched by over-approximation, not enumerated exactly`);
72
+ }
73
+ }
74
+ function detectUndecidable(predicates) {
75
+ const reasons = new Set();
76
+ for (const p of predicates) {
77
+ if (p === null || typeof p !== 'object')
78
+ continue;
79
+ const r = p.resource;
80
+ if (typeof r === 'string' && r.startsWith('re:')) {
81
+ reasons.add('re: regex resource matcher — resource dimension cannot be soundly enumerated');
82
+ }
83
+ if (Array.isArray(p.where))
84
+ for (const c of p.where)
85
+ conditionUndecidable(c, reasons);
86
+ }
87
+ return [...reasons];
88
+ }
89
+ function toModel(p) {
90
+ const { predicates, caveats } = normalizePolicy(p);
91
+ const ca = (0, caveats_1.analyzeCaveats)(caveats);
92
+ return {
93
+ predicates,
94
+ caveats,
95
+ caveatSat: ca.sat,
96
+ caveatReason: ca.reason,
97
+ revCap: ca.revCap,
98
+ undecidable: detectUndecidable(predicates),
99
+ };
100
+ }
101
+ function addVerbLiterals(c, into) {
102
+ const v = c.value;
103
+ if (c.op === 'eq' || c.op === 'ne' || c.op === 'prefix' || c.op === 'like' || c.op === 'is_a') {
104
+ if (typeof v === 'string')
105
+ into.add(v);
106
+ }
107
+ else if ((c.op === 'in' || c.op === 'nin') && Array.isArray(v)) {
108
+ for (const x of v)
109
+ if (typeof x === 'string')
110
+ into.add(x);
111
+ }
112
+ }
113
+ function collectProbes(inputs) {
114
+ const verbLiterals = new Set(inputs.extraVerbs ?? []);
115
+ const resourceLiterals = new Set();
116
+ const fieldConds = new Map();
117
+ const addFieldCond = (path, c) => {
118
+ const arr = fieldConds.get(path) ?? [];
119
+ arr.push(c);
120
+ fieldConds.set(path, arr);
121
+ };
122
+ const classifyResource = (r) => {
123
+ if (r === undefined || r === '*')
124
+ return; // universal: FRESH_RESOURCE represents it
125
+ if (r.startsWith('re:'))
126
+ return; // undecidable: FRESH_RESOURCE stands in (flagged on the model)
127
+ if (r.endsWith('*')) {
128
+ const body = r.slice(0, -1);
129
+ resourceLiterals.add(body);
130
+ resourceLiterals.add(body + 'x');
131
+ return;
132
+ }
133
+ resourceLiterals.add(r);
134
+ };
135
+ const handleCond = (c) => {
136
+ if (c === null || typeof c !== 'object')
137
+ return;
138
+ // Recurse through boolean groupings and bucket their leaves (keeps the grid distinguishing for
139
+ // nested boolean logic over decidable ops).
140
+ if ((0, pca_1.isGroupCondition)(c)) {
141
+ if ('all_of' in c && Array.isArray(c.all_of))
142
+ for (const x of c.all_of)
143
+ handleCond(x);
144
+ else if ('any_of' in c && Array.isArray(c.any_of))
145
+ for (const x of c.any_of)
146
+ handleCond(x);
147
+ else if ('not' in c)
148
+ handleCond(c.not);
149
+ return;
150
+ }
151
+ if (!(0, pca_1.isLeafCondition)(c))
152
+ return;
153
+ if (c.field === 'action.verb') {
154
+ addVerbLiterals(c, verbLiterals);
155
+ return;
156
+ }
157
+ if (c.field === 'action.resource') {
158
+ if (c.op === 'prefix' && typeof c.value === 'string')
159
+ classifyResource(c.value + '*');
160
+ else if (typeof c.value === 'string')
161
+ classifyResource(c.value);
162
+ else if (Array.isArray(c.value))
163
+ for (const x of c.value)
164
+ if (typeof x === 'string')
165
+ classifyResource(x);
166
+ return;
167
+ }
168
+ addFieldCond(c.field, c);
169
+ };
170
+ for (const list of inputs.predicateSets) {
171
+ for (const p of list) {
172
+ if (p === null || typeof p !== 'object')
173
+ continue;
174
+ if (typeof p.verb === 'string') {
175
+ if (p.verb !== '*')
176
+ verbLiterals.add(p.verb);
177
+ }
178
+ else if (Array.isArray(p.verb)) {
179
+ for (const v of p.verb)
180
+ if (typeof v === 'string' && v !== '*')
181
+ verbLiterals.add(v);
182
+ }
183
+ classifyResource(p.resource);
184
+ if (Array.isArray(p.where))
185
+ for (const c of p.where)
186
+ handleCond(c);
187
+ }
188
+ }
189
+ for (const r of inputs.extraResources ?? [])
190
+ classifyResource(r);
191
+ for (const ec of inputs.extraFieldConditions ?? [])
192
+ handleCond({ ...ec.cond, field: ec.path });
193
+ const fields = [...fieldConds.entries()].map(([path, conds]) => ({
194
+ path,
195
+ values: [...(0, field_1.fieldProbes)(conds), ABSENT],
196
+ }));
197
+ return {
198
+ verbs: [...verbLiterals, FRESH_VERB],
199
+ resources: [...resourceLiterals, FRESH_RESOURCE],
200
+ revClasses: inputs.varyReversibility ? [...types_1.REVERSIBILITY_ORDER] : [undefined],
201
+ fields,
202
+ };
203
+ }
204
+ function productSize(pr) {
205
+ let n = pr.verbs.length * pr.resources.length * pr.revClasses.length;
206
+ for (const f of pr.fields)
207
+ n *= f.values.length;
208
+ return n;
209
+ }
210
+ function* enumerateFields(fields, i, acc) {
211
+ if (i >= fields.length) {
212
+ yield new Map(acc);
213
+ return;
214
+ }
215
+ const f = fields[i];
216
+ if (f === undefined) {
217
+ yield new Map(acc);
218
+ return;
219
+ }
220
+ for (const v of f.values) {
221
+ if (v === ABSENT) {
222
+ acc.delete(f.path);
223
+ }
224
+ else {
225
+ acc.set(f.path, v);
226
+ }
227
+ yield* enumerateFields(fields, i + 1, acc);
228
+ }
229
+ acc.delete(f.path);
230
+ }
231
+ function* representatives(pr) {
232
+ for (const verb of pr.verbs) {
233
+ for (const resource of pr.resources) {
234
+ for (const revClass of pr.revClasses) {
235
+ for (const assignments of enumerateFields(pr.fields, 0, new Map())) {
236
+ yield { verb, resource, revClass, assignments };
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ function branch() {
243
+ return { leaf: false, children: new Map() };
244
+ }
245
+ function insert(root, segs, value) {
246
+ let node = root;
247
+ for (let i = 0; i < segs.length - 1; i++) {
248
+ const key = segs[i];
249
+ if (key === undefined || node.leaf)
250
+ return;
251
+ const next = node.children.get(key);
252
+ if (next === undefined || next.leaf) {
253
+ const created = branch();
254
+ node.children.set(key, created);
255
+ node = created;
256
+ }
257
+ else {
258
+ node = next;
259
+ }
260
+ }
261
+ const last = segs[segs.length - 1];
262
+ if (last !== undefined && !node.leaf)
263
+ node.children.set(last, { leaf: true, value });
264
+ }
265
+ function materialize(node) {
266
+ const out = {};
267
+ if (node.leaf)
268
+ return out;
269
+ for (const [key, child] of node.children) {
270
+ out[key] = child.leaf ? child.value : materialize(child);
271
+ }
272
+ return out;
273
+ }
274
+ function buildRoots(rep) {
275
+ const roots = new Map([
276
+ ['action', branch()],
277
+ ['subject', branch()],
278
+ ['env', branch()],
279
+ ]);
280
+ for (const [path, value] of rep.assignments) {
281
+ const segs = path.split('.');
282
+ const first = segs[0];
283
+ if (first === undefined || segs.length < 2)
284
+ continue;
285
+ const root = roots.get(first);
286
+ if (root === undefined)
287
+ continue; // only action / subject / env roots are addressable
288
+ insert(root, segs.slice(1), value);
289
+ }
290
+ const actionTree = roots.get('action');
291
+ const subjectTree = roots.get('subject');
292
+ const envTree = roots.get('env');
293
+ const action = { verb: rep.verb, resource: rep.resource, params: {} };
294
+ if (actionTree !== undefined && !actionTree.leaf) {
295
+ for (const [key, child] of actionTree.children) {
296
+ if (key === 'verb' || key === 'resource')
297
+ continue; // fixed from the rep
298
+ if (key === 'params') {
299
+ // `action.params.*` branches materialize to a typed record; a whole-`params` leaf is flagged
300
+ // undecidable upstream, so it is safe to ignore here (params stays an empty, present object).
301
+ if (!child.leaf)
302
+ action.params = materialize(child);
303
+ }
304
+ else {
305
+ action[key] = child.leaf ? child.value : materialize(child);
306
+ }
307
+ }
308
+ }
309
+ if (rep.revClass !== undefined)
310
+ action.reversibility_class = rep.revClass;
311
+ const subject = subjectTree === undefined || subjectTree.leaf ? {} : materialize(subjectTree);
312
+ const env = envTree === undefined || envTree.leaf ? {} : materialize(envTree);
313
+ return { action, subject, env };
314
+ }
315
+ function contextFromRoots(roots) {
316
+ return { action: roots.action, subject: roots.subject, env: roots.env };
317
+ }
318
+ function reversibilityOk(model, revClass) {
319
+ if (model.revCap === undefined)
320
+ return true;
321
+ if (revClass === undefined)
322
+ return false; // a reversibility_max caveat denies an action with no class
323
+ const idx = types_1.REVERSIBILITY_ORDER.indexOf(revClass);
324
+ return idx >= 0 && idx <= model.revCap;
325
+ }
326
+ /** Does the policy admit this representative action? (predicate match AND static caveat gate.) */
327
+ function admits(model, rep) {
328
+ if (!model.caveatSat)
329
+ return false;
330
+ if (!reversibilityOk(model, rep.revClass))
331
+ return false;
332
+ const ctx = contextFromRoots(buildRoots(rep));
333
+ return (0, pca_1.evaluatePredicates)(model.predicates, ctx).allowed;
334
+ }
335
+ /** Concrete admission of a fully-specified action (used by reachable's fast path; exact). */
336
+ function admitsAction(model, action) {
337
+ if (!model.caveatSat)
338
+ return false;
339
+ if (!reversibilityOk(model, action.reversibilityClass))
340
+ return false;
341
+ const act = { verb: action.verb, resource: action.resource, params: { ...(action.params ?? {}) } };
342
+ if (action.reversibilityClass !== undefined)
343
+ act.reversibility_class = action.reversibilityClass;
344
+ const ctx = {
345
+ action: act,
346
+ subject: { ...(action.subject ?? {}) },
347
+ env: { ...(action.env ?? {}) },
348
+ };
349
+ return (0, pca_1.evaluatePredicates)(model.predicates, ctx).allowed;
350
+ }
351
+ function repToAction(rep) {
352
+ const roots = buildRoots(rep);
353
+ const action = { verb: rep.verb, resource: rep.resource, params: { ...roots.action.params } };
354
+ if (rep.revClass !== undefined)
355
+ action.reversibilityClass = rep.revClass;
356
+ if (Object.keys(roots.subject).length > 0)
357
+ action.subject = roots.subject;
358
+ if (Object.keys(roots.env).length > 0)
359
+ action.env = roots.env;
360
+ return action;
361
+ }
362
+ /**
363
+ * Walk the representative grid, stopping at the first rep for which `predicate` is true. Returns
364
+ * `overflow: true` (without walking) when the grid is larger than `max`.
365
+ */
366
+ function search(pr, predicate, max) {
367
+ if (productSize(pr) > max)
368
+ return { overflow: true };
369
+ for (const rep of representatives(pr)) {
370
+ if (predicate(rep))
371
+ return { hit: rep, overflow: false };
372
+ }
373
+ return { overflow: false };
374
+ }
375
+ //# sourceMappingURL=enumerate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../src/enumerate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type Condition, type ConditionOp } from '@atlasauth/pca';
2
+ /**
3
+ * The interval + finite-set engine for a single `where` field.
4
+ *
5
+ * PCA's `where` conditions over one field are a conjunction of threshold tests (lt/lte/gt/gte/eq on
6
+ * numbers), membership tests (eq/ne/in/nin over finite values) and string-prefix tests. Any such
7
+ * conjunction partitions the value line into finitely many CELLS on which every condition is constant,
8
+ * bounded by the literals that appear. Picking one representative value per cell is therefore a SOUND
9
+ * AND COMPLETE probe set for the decided fragment: two policies agree on all actions iff they agree on
10
+ * all actions drawn from these representatives (the finite-distinguishing-value property that makes the
11
+ * relational queries decidable by enumeration — see enumerate.ts).
12
+ *
13
+ * `ref` conditions (operand is another field) are NOT a single-field constraint; they are flagged
14
+ * upstream as undecidable and skipped here.
15
+ */
16
+ /** A sentinel string guaranteed not to equal, be a prefix of, or be prefixed by any policy literal. */
17
+ export declare const FRESH_STRING = "\0atlas-analyzer::fresh-unmatched\0";
18
+ /**
19
+ * Compute the representative probe values for one field from the conjunction of `where` conditions on
20
+ * it. The result is finite and distinguishing for the decided fragment. The caller adds an `absent`
21
+ * representative separately (to probe `exists` and the params-absent fail-closed semantics).
22
+ */
23
+ export declare function fieldProbes(conditions: readonly Condition[]): unknown[];
24
+ export interface NumInterval {
25
+ lo: number;
26
+ loInclusive: boolean;
27
+ hi: number;
28
+ hiInclusive: boolean;
29
+ }
30
+ export declare const FULL_INTERVAL: NumInterval;
31
+ /** Tighten an interval by one numeric condition. Returns the same interval for non-numeric operands. */
32
+ export declare function tightenInterval(iv: NumInterval, op: ConditionOp, value: unknown): NumInterval;
33
+ /** An interval is empty iff lo > hi, or lo == hi with either bound exclusive. */
34
+ export declare function intervalEmpty(iv: NumInterval): boolean;
35
+ /**
36
+ * Decide whether the numeric subset of a field's `where` conjunction is satisfiable. Detects, e.g.,
37
+ * `amount >= 100 AND amount <= 10` as unsatisfiable (empty interval). Conditions with non-numeric or
38
+ * `ref` operands, and boolean groupings (all_of/any_of/not), do not participate (they are decided by
39
+ * the finite-set / enumeration path); ignoring them only ever widens the interval, so a satisfiable
40
+ * verdict here is sound as a necessary condition.
41
+ */
42
+ export declare function numericConjunctionSatisfiable(conditions: readonly Condition[]): boolean;
package/dist/field.js ADDED
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FULL_INTERVAL = exports.FRESH_STRING = void 0;
4
+ exports.fieldProbes = fieldProbes;
5
+ exports.tightenInterval = tightenInterval;
6
+ exports.intervalEmpty = intervalEmpty;
7
+ exports.numericConjunctionSatisfiable = numericConjunctionSatisfiable;
8
+ const pca_1 = require("@atlasauth/pca");
9
+ /**
10
+ * The interval + finite-set engine for a single `where` field.
11
+ *
12
+ * PCA's `where` conditions over one field are a conjunction of threshold tests (lt/lte/gt/gte/eq on
13
+ * numbers), membership tests (eq/ne/in/nin over finite values) and string-prefix tests. Any such
14
+ * conjunction partitions the value line into finitely many CELLS on which every condition is constant,
15
+ * bounded by the literals that appear. Picking one representative value per cell is therefore a SOUND
16
+ * AND COMPLETE probe set for the decided fragment: two policies agree on all actions iff they agree on
17
+ * all actions drawn from these representatives (the finite-distinguishing-value property that makes the
18
+ * relational queries decidable by enumeration — see enumerate.ts).
19
+ *
20
+ * `ref` conditions (operand is another field) are NOT a single-field constraint; they are flagged
21
+ * upstream as undecidable and skipped here.
22
+ */
23
+ /** A sentinel string guaranteed not to equal, be a prefix of, or be prefixed by any policy literal. */
24
+ exports.FRESH_STRING = '\u0000atlas-analyzer::fresh-unmatched\u0000';
25
+ function isFiniteNumber(v) {
26
+ return typeof v === 'number' && Number.isFinite(v);
27
+ }
28
+ /**
29
+ * Representatives of a numeric threshold arrangement: below the min, each threshold exactly, the open
30
+ * interval between each pair of consecutive thresholds (its midpoint), and above the max. This is the
31
+ * minimal set on which every `x {lt,lte,gt,gte,eq} t` built from these thresholds is constant per cell.
32
+ */
33
+ function numericCellReps(thresholds) {
34
+ const sorted = [...new Set(thresholds)].filter(Number.isFinite).sort((a, b) => a - b);
35
+ if (sorted.length === 0)
36
+ return [];
37
+ const first = sorted[0];
38
+ const last = sorted[sorted.length - 1];
39
+ if (first === undefined || last === undefined)
40
+ return [];
41
+ const out = [first - 1];
42
+ for (let i = 0; i < sorted.length; i++) {
43
+ const ti = sorted[i];
44
+ if (ti === undefined)
45
+ continue;
46
+ out.push(ti);
47
+ const tn = sorted[i + 1];
48
+ if (tn !== undefined)
49
+ out.push((ti + tn) / 2);
50
+ }
51
+ out.push(last + 1);
52
+ return out;
53
+ }
54
+ /** A stable key for de-duplicating representative values of mixed type. */
55
+ function valueKey(x) {
56
+ if (x === exports.FRESH_STRING)
57
+ return 's:__fresh__';
58
+ try {
59
+ return typeof x + ':' + JSON.stringify(x);
60
+ }
61
+ catch {
62
+ return typeof x + ':<unserializable>';
63
+ }
64
+ }
65
+ /**
66
+ * Compute the representative probe values for one field from the conjunction of `where` conditions on
67
+ * it. The result is finite and distinguishing for the decided fragment. The caller adds an `absent`
68
+ * representative separately (to probe `exists` and the params-absent fail-closed semantics).
69
+ */
70
+ function fieldProbes(conditions) {
71
+ const literals = [];
72
+ const thresholds = [];
73
+ const prefixes = [];
74
+ let hasNumericOp = false;
75
+ for (const c of conditions) {
76
+ // Group conditions (all_of/any_of/not) are flattened to their leaves upstream by `collectProbes`;
77
+ // a cross-field `ref` relation is undecidable and also handled upstream. Skip either here.
78
+ if (!(0, pca_1.isLeafCondition)(c) || c.ref !== undefined)
79
+ continue;
80
+ const v = c.value;
81
+ switch (c.op) {
82
+ case 'eq':
83
+ case 'ne':
84
+ literals.push(v);
85
+ if (isFiniteNumber(v)) {
86
+ thresholds.push(v);
87
+ hasNumericOp = true;
88
+ }
89
+ break;
90
+ case 'in':
91
+ case 'nin':
92
+ if (Array.isArray(v)) {
93
+ for (const x of v) {
94
+ literals.push(x);
95
+ if (isFiniteNumber(x)) {
96
+ thresholds.push(x);
97
+ hasNumericOp = true;
98
+ }
99
+ }
100
+ }
101
+ break;
102
+ case 'lt':
103
+ case 'lte':
104
+ case 'gt':
105
+ case 'gte':
106
+ if (isFiniteNumber(v)) {
107
+ thresholds.push(v);
108
+ hasNumericOp = true;
109
+ }
110
+ break;
111
+ case 'prefix':
112
+ case 'like':
113
+ case 'matches':
114
+ case 'is_a':
115
+ // The literal and a strictly-extending variant give the enumerator something to hit; the op
116
+ // itself is flagged undecidable, so the verdict fails safe regardless.
117
+ if (typeof v === 'string') {
118
+ literals.push(v);
119
+ if (c.op === 'prefix')
120
+ prefixes.push(v);
121
+ }
122
+ break;
123
+ case 'member_of':
124
+ if (typeof v === 'string')
125
+ literals.push(v);
126
+ else if (Array.isArray(v))
127
+ for (const x of v)
128
+ if (typeof x === 'string')
129
+ literals.push(x);
130
+ break;
131
+ case 'exists':
132
+ default:
133
+ break;
134
+ }
135
+ }
136
+ const out = [];
137
+ const seen = new Set();
138
+ const add = (x) => {
139
+ const k = valueKey(x);
140
+ if (!seen.has(k)) {
141
+ seen.add(k);
142
+ out.push(x);
143
+ }
144
+ };
145
+ for (const l of literals)
146
+ add(l);
147
+ for (const n of numericCellReps(thresholds))
148
+ add(n);
149
+ for (const p of prefixes) {
150
+ add(p);
151
+ add(p + 'x'); // a value strictly extending the prefix (distinguishes prefix from exact)
152
+ }
153
+ add(exports.FRESH_STRING); // a value matching no eq/in/prefix literal
154
+ if (hasNumericOp && thresholds.length > 0) {
155
+ const mx = Math.max(...thresholds);
156
+ if (Number.isFinite(mx))
157
+ add(mx + 1); // a value above every numeric threshold
158
+ }
159
+ return out;
160
+ }
161
+ exports.FULL_INTERVAL = { lo: -Infinity, loInclusive: false, hi: Infinity, hiInclusive: false };
162
+ /** Tighten an interval by one numeric condition. Returns the same interval for non-numeric operands. */
163
+ function tightenInterval(iv, op, value) {
164
+ if (!isFiniteNumber(value))
165
+ return iv;
166
+ const next = { ...iv };
167
+ switch (op) {
168
+ case 'eq':
169
+ if (value > next.lo || (value === next.lo && !next.loInclusive)) {
170
+ next.lo = value;
171
+ next.loInclusive = true;
172
+ }
173
+ if (value < next.hi || (value === next.hi && !next.hiInclusive)) {
174
+ next.hi = value;
175
+ next.hiInclusive = true;
176
+ }
177
+ break;
178
+ case 'gte':
179
+ // A stricter existing lower bound (gt at the same value) is kept; only a higher value binds.
180
+ if (value > next.lo) {
181
+ next.lo = value;
182
+ next.loInclusive = true;
183
+ }
184
+ break;
185
+ case 'gt':
186
+ if (value > next.lo || (value === next.lo && next.loInclusive)) {
187
+ next.lo = value;
188
+ next.loInclusive = false;
189
+ }
190
+ break;
191
+ case 'lte':
192
+ if (value < next.hi) {
193
+ next.hi = value;
194
+ next.hiInclusive = true;
195
+ }
196
+ break;
197
+ case 'lt':
198
+ if (value < next.hi || (value === next.hi && next.hiInclusive)) {
199
+ next.hi = value;
200
+ next.hiInclusive = false;
201
+ }
202
+ break;
203
+ default:
204
+ break;
205
+ }
206
+ return next;
207
+ }
208
+ /** An interval is empty iff lo > hi, or lo == hi with either bound exclusive. */
209
+ function intervalEmpty(iv) {
210
+ if (iv.lo > iv.hi)
211
+ return true;
212
+ if (iv.lo === iv.hi)
213
+ return !(iv.loInclusive && iv.hiInclusive);
214
+ return false;
215
+ }
216
+ /**
217
+ * Decide whether the numeric subset of a field's `where` conjunction is satisfiable. Detects, e.g.,
218
+ * `amount >= 100 AND amount <= 10` as unsatisfiable (empty interval). Conditions with non-numeric or
219
+ * `ref` operands, and boolean groupings (all_of/any_of/not), do not participate (they are decided by
220
+ * the finite-set / enumeration path); ignoring them only ever widens the interval, so a satisfiable
221
+ * verdict here is sound as a necessary condition.
222
+ */
223
+ function numericConjunctionSatisfiable(conditions) {
224
+ let iv = exports.FULL_INTERVAL;
225
+ for (const c of conditions) {
226
+ if (!(0, pca_1.isLeafCondition)(c) || c.ref !== undefined)
227
+ continue;
228
+ iv = tightenInterval(iv, c.op, c.value);
229
+ }
230
+ return !intervalEmpty(iv);
231
+ }
232
+ //# sourceMappingURL=field.js.map