1'use strict';2// Machine-checkable output applicability (`outputs[].appliesWhen`).3//4// A predicate over the decision's own canonical source locations (lib/source-location.js),5// evaluated against the source record. It states when an output is written at all; it never6// computes the written value. Grammar (closed: every node has exactly these keys):7// {op:'equals', location, value} value: string | finite number | boolean8// {op:'notEquals', location, value}9// {op:'in', location, values:[...]} non-empty, distinct scalars10// {op:'populated', location}11// {op:'notPopulated',location}12// {op:'all', of:[...]} / {op:'any', of:[...]} non-empty13// Over an array location the comparison is existential: equals/in/populated hold when any value
44 if (typeof p.location !== 'string' || !L.isCanonical(p.location)) errors.push(`${at} location ${JSON.stringify(p.location)} is not a canonical location`);
45 else if (!own.has(p.location)) errors.push(`${at} location ${p.location} is not one of the decision's source locations`);
46 if (LEAF[p.op].includes('value') && Object.hasOwn(p, 'value') && !scalar(p.value)) errors.push(`${at} value must be a string, finite number or boolean`);
47 if (p.op === 'in' && Object.hasOwn(p, 'values') && (!Array.isArray(p.values) || !p.values.length || p.values.some(v => !scalar(v)) || new Set(p.values.map(v => JSON.stringify(v))).size !== p.values.length)) errors.push(`${at} values must be a non-empty list of distinct scalars`);
48 }
49 walk(pred, 0, 'appliesWhen');
50 return errors;
51}
52// Locations a (valid) predicate reads.
53function locations(pred, out = new Set()) {
54 if (pred && GROUP.includes(pred.op)) for (const c of pred.of || []) locations(c, out);
55 else if (pred?.location) out.add(pred.location);
56 return out;
57}
58function evaluate(pred, record) {
59 switch (pred.op) {
60 case 'all': return pred.of.every(c => evaluate(c, record));
61 case 'any': return pred.of.some(c => evaluate(c, record));