@cullet/erp-core 1.0.1 → 1.0.3

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 (144) hide show
  1. package/README.md +24 -24
  2. package/dist/gate-engine-registry.js.map +1 -1
  3. package/dist/gate-v1-payload.schema.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/outcome.js.map +1 -1
  8. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  9. package/dist/policy-service.js.map +1 -1
  10. package/dist/validation-code.js.map +1 -1
  11. package/dist/validation-error.js.map +1 -1
  12. package/dist/validation-field.js.map +1 -1
  13. package/meta.json +67 -62
  14. package/package.json +56 -56
  15. package/src/core/application/commands/command.ts +3 -3
  16. package/src/core/application/commands/requested-by.ts +60 -60
  17. package/src/core/application/index.ts +21 -21
  18. package/src/core/application/policy-error-mapper.ts +54 -54
  19. package/src/core/application/ports/index.ts +6 -6
  20. package/src/core/application/ports/logger.port.ts +4 -4
  21. package/src/core/application/ports/metrics.port.ts +3 -3
  22. package/src/core/application/ports/policy-port.ts +7 -7
  23. package/src/core/application/ports/repository.port.ts +3 -3
  24. package/src/core/application/ports/temporal-repository.port.ts +4 -4
  25. package/src/core/application/ports/tracer.port.ts +7 -7
  26. package/src/core/application/queries/query.ts +13 -13
  27. package/src/core/application/temporal/index.ts +7 -7
  28. package/src/core/application/temporal/temporal-context.ts +20 -20
  29. package/src/core/application/temporal/temporal-use-case.ts +22 -22
  30. package/src/core/application/use-case.ts +10 -10
  31. package/src/core/config/core-config.instance.ts +1 -1
  32. package/src/core/config/core-config.ts +59 -59
  33. package/src/core/config/index.ts +6 -6
  34. package/src/core/config/policy-reporter.ts +31 -31
  35. package/src/core/config/silent-policy-reporter.ts +1 -1
  36. package/src/core/domain/entity.ts +44 -44
  37. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +6 -6
  38. package/src/core/domain/rulesets/ruleset-registry.ts +49 -49
  39. package/src/core/domain/rulesets/ruleset.contracts.ts +6 -6
  40. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +5 -5
  41. package/src/core/domain/temporal/index.ts +11 -11
  42. package/src/core/domain/temporal/temporal-range.ts +16 -16
  43. package/src/core/domain/temporal/temporal-snapshot.ts +24 -24
  44. package/src/core/domain/temporal/transaction-time.ts +30 -30
  45. package/src/core/domain/temporal/valid-time.ts +29 -29
  46. package/src/core/domain/value-object.ts +16 -16
  47. package/src/core/errors/app-error.ts +64 -64
  48. package/src/core/errors/authentication-error.ts +134 -134
  49. package/src/core/errors/authorization-error.ts +154 -154
  50. package/src/core/errors/business-rule-violation-error.ts +15 -15
  51. package/src/core/errors/conflict-error.ts +202 -201
  52. package/src/core/errors/error-codes.ts +48 -48
  53. package/src/core/errors/idempotency-error.ts +324 -320
  54. package/src/core/errors/index.ts +50 -50
  55. package/src/core/errors/integration-error.ts +120 -120
  56. package/src/core/errors/legacy-incompatible-error.ts +13 -13
  57. package/src/core/errors/not-found-error.ts +14 -14
  58. package/src/core/errors/serialization-error.ts +167 -167
  59. package/src/core/errors/temporal-error.ts +109 -107
  60. package/src/core/errors/types.ts +35 -35
  61. package/src/core/errors/unexpected-error.ts +10 -10
  62. package/src/core/errors/utils/index.ts +2 -2
  63. package/src/core/errors/utils/json-safe.ts +53 -53
  64. package/src/core/errors/validation-error.ts +18 -18
  65. package/src/core/exceptions/business-rule-violation-exception.ts +6 -6
  66. package/src/core/exceptions/domain-exception.ts +5 -5
  67. package/src/core/exceptions/entity-not-found-exception.ts +6 -6
  68. package/src/core/exceptions/invalid-state-transition-exception.ts +8 -8
  69. package/src/core/exceptions/invariant-violation-exception.ts +3 -3
  70. package/src/core/exceptions/validation-code.ts +35 -29
  71. package/src/core/exceptions/validation-exception.ts +19 -19
  72. package/src/core/exceptions/validation-field.ts +20 -19
  73. package/src/core/index.ts +7 -7
  74. package/src/core/policies/asof/asof.ts +47 -47
  75. package/src/core/policies/catalog/catalog.instance.ts +8 -6
  76. package/src/core/policies/catalog/index.ts +4 -4
  77. package/src/core/policies/catalog/policy-catalog-entry.ts +199 -197
  78. package/src/core/policies/catalog/policy-catalog.ts +101 -101
  79. package/src/core/policies/catalog/policy-key.ts +51 -51
  80. package/src/core/policies/context/context-builder.ts +335 -313
  81. package/src/core/policies/context/context-registry.instance.ts +9 -9
  82. package/src/core/policies/context/context-registry.ts +61 -61
  83. package/src/core/policies/context/context-resolver.ts +12 -12
  84. package/src/core/policies/context/context-seed.ts +19 -15
  85. package/src/core/policies/context/index.ts +9 -9
  86. package/src/core/policies/context/path.ts +135 -128
  87. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +56 -55
  88. package/src/core/policies/defs/index.ts +10 -10
  89. package/src/core/policies/defs/policy-definition-repository.ts +3 -3
  90. package/src/core/policies/defs/policy-definition.ts +143 -142
  91. package/src/core/policies/defs/policy-scope.ts +21 -19
  92. package/src/core/policies/defs/policy-semver.ts +51 -51
  93. package/src/core/policies/engines/compute-engine-registry.ts +43 -43
  94. package/src/core/policies/engines/compute-evaluator-registry.ts +20 -20
  95. package/src/core/policies/engines/compute-registry.ts +45 -45
  96. package/src/core/policies/engines/compute-types.ts +15 -15
  97. package/src/core/policies/engines/condition-evaluator-reporter.ts +17 -17
  98. package/src/core/policies/engines/gate-engine-registry.ts +27 -25
  99. package/src/core/policies/engines/gate-types.ts +28 -28
  100. package/src/core/policies/engines/index.ts +28 -28
  101. package/src/core/policies/engines/parse-compute-payload.ts +51 -45
  102. package/src/core/policies/engines/parse-gate-payload.ts +42 -42
  103. package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +39 -37
  104. package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +43 -41
  105. package/src/core/policies/engines/v1/compute/index.ts +6 -6
  106. package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +41 -39
  107. package/src/core/policies/engines/v1/condition-evaluator.ts +547 -505
  108. package/src/core/policies/engines/v1/condition-schema.ts +35 -35
  109. package/src/core/policies/engines/v1/condition-types.ts +21 -21
  110. package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +134 -130
  111. package/src/core/policies/engines/v1/gate/gate-types-v1.ts +5 -5
  112. package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +32 -30
  113. package/src/core/policies/engines/v1/gate/index.ts +4 -4
  114. package/src/core/policies/engines/v1/index.ts +8 -8
  115. package/src/core/policies/index.ts +72 -72
  116. package/src/core/policies/package/policy-package.ts +3 -3
  117. package/src/core/policies/policy-ids.ts +25 -25
  118. package/src/core/policies/resolver/policy-resolver.ts +34 -34
  119. package/src/core/policies/service/index.ts +6 -6
  120. package/src/core/policies/service/policy-evaluation-error.ts +141 -133
  121. package/src/core/policies/service/policy-service.ts +432 -419
  122. package/src/core/policies/utils/date.ts +3 -3
  123. package/src/core/policies/utils/hash.ts +61 -61
  124. package/src/core/result/outcome.ts +131 -126
  125. package/src/core/result/result.ts +130 -127
  126. package/src/core/shared/aggregate-version.ts +5 -5
  127. package/src/core/shared/hashing.ts +18 -18
  128. package/src/core/shared/immutable.ts +29 -29
  129. package/src/core/shared/temporal-guards.ts +9 -9
  130. package/src/core/versioning/domain-event-contracts.ts +40 -40
  131. package/src/core/versioning/version.ts +19 -19
  132. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +18 -18
  133. package/src/examples/rulesets/entity/order-invariants-v1.ts +31 -29
  134. package/src/examples/rulesets/entity/order-invariants-v2.ts +12 -12
  135. package/src/examples/rulesets/entity/order.ts +127 -127
  136. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +41 -39
  137. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +14 -12
  138. package/src/examples/rulesets/value-object/cpf.ts +24 -24
  139. package/src/examples/rulesets/value-object/customer.ts +42 -39
  140. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +27 -27
  141. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +13 -13
  142. package/src/examples/rulesets/value-object/person-name.ts +29 -26
  143. package/src/index.ts +2 -2
  144. package/src/version.ts +1 -1
@@ -3,590 +3,632 @@ import { PolicyDateUtils } from "../../utils";
3
3
  import { Result } from "../../../result/result";
4
4
 
5
5
  import type {
6
- ConditionAndNode,
7
- ConditionLeafNode,
8
- ConditionNode,
9
- ConditionNotNode,
10
- ConditionOp,
11
- ConditionOrNode,
6
+ ConditionAndNode,
7
+ ConditionLeafNode,
8
+ ConditionNode,
9
+ ConditionNotNode,
10
+ ConditionOp,
11
+ ConditionOrNode,
12
12
  } from "./condition-types";
13
13
  import type {
14
- ConditionEvaluationOptions,
15
- ConditionEvaluationReport,
14
+ ConditionEvaluationOptions,
15
+ ConditionEvaluationReport,
16
16
  } from "../condition-evaluator-reporter";
17
17
  import type { PolicyContext } from "../gate-types";
18
18
 
19
19
  const CONDITION_EVAL_THROWN_TAG = "CONDITION_EVAL_THREW";
20
20
  const NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG =
21
- "NULLISH_NUMERIC_OPERAND_NOT_ALLOWED";
21
+ "NULLISH_NUMERIC_OPERAND_NOT_ALLOWED";
22
22
  const NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG = "NULLISH_DATE_OPERAND_NOT_ALLOWED";
23
23
  const INVALID_DATE_OPERAND_TAG = "INVALID_DATE_OPERAND";
24
24
  const EMPTY_OR_CONDITION_TAG = "EMPTY_OR_CONDITION";
25
25
  const ISO_8601_UTC_DATE_PATTERN =
26
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
26
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
27
27
 
28
28
  export interface ConditionEvaluationTrace {
29
- readonly conditionPath: string;
30
- readonly decisiveNode: ConditionNode;
31
- readonly lastEvaluatedLeaf: ConditionLeafNode;
32
- readonly lastEvaluatedLeafPath: string;
33
- readonly lastEvaluatedLeafResult: boolean;
29
+ readonly conditionPath: string;
30
+ readonly decisiveNode: ConditionNode;
31
+ readonly lastEvaluatedLeaf: ConditionLeafNode;
32
+ readonly lastEvaluatedLeafPath: string;
33
+ readonly lastEvaluatedLeafResult: boolean;
34
34
  }
35
35
 
36
36
  export interface TracedConditionEvaluation {
37
- readonly matched: boolean;
38
- readonly trace: ConditionEvaluationTrace;
37
+ readonly matched: boolean;
38
+ readonly trace: ConditionEvaluationTrace;
39
39
  }
40
40
 
41
41
  type RelationalOperator = Extract<ConditionOp, "gt" | "gte" | "lt" | "lte">;
42
42
 
43
43
  export class ConditionEvaluatorV1 {
44
- constructor(
45
- private readonly context: PolicyContext,
46
- private readonly options: ConditionEvaluationOptions,
47
- ) {}
48
-
49
- private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
50
- return "field" in node && "op" in node;
51
- }
52
-
53
- private static isAndNode(node: ConditionNode): node is ConditionAndNode {
54
- return "and" in node;
55
- }
56
-
57
- private static isOrNode(node: ConditionNode): node is ConditionOrNode {
58
- return "or" in node;
59
- }
60
-
61
- private static isNotNode(node: ConditionNode): node is ConditionNotNode {
62
- return "not" in node;
63
- }
64
-
65
- private static isRelationalOperator(
66
- op: ConditionOp,
67
- ): op is RelationalOperator {
68
- return op === "gt" || op === "gte" || op === "lt" || op === "lte";
69
- }
70
-
71
- private static describeError(error: unknown): {
72
- name: string;
73
- message: string;
74
- } {
75
- if (error instanceof Error) {
76
- return {
77
- name: error.name,
78
- message: error.message,
79
- };
80
- }
44
+ constructor(
45
+ private readonly context: PolicyContext,
46
+ private readonly options: ConditionEvaluationOptions,
47
+ ) {}
81
48
 
82
- return {
83
- name: "NonErrorThrown",
84
- message: String(error),
85
- };
86
- }
87
-
88
- private static buildNullishNumericOperandMessage(
89
- node: ConditionLeafNode,
90
- actual: null | undefined,
91
- ): string {
92
- const resolvedValue = actual === null ? "null" : "undefined";
93
-
94
- return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
95
- }
96
-
97
- private static buildNullishDateOperandMessage(
98
- node: ConditionLeafNode,
99
- actual: null | undefined,
100
- ): string {
101
- const resolvedValue = actual === null ? "null" : "undefined";
102
-
103
- return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
104
- }
105
-
106
- private static buildInvalidDateOperandMessage(
107
- node: ConditionLeafNode,
108
- ): string {
109
- return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
110
- }
111
-
112
- private static buildEmptyOrConditionMessage(): string {
113
- return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
114
- }
115
-
116
- private static parseComparableDate(
117
- value: unknown,
118
- ): Result<Date, string> | null {
119
- if (value instanceof Date) {
120
- if (!PolicyDateUtils.isValid(value)) {
121
- return Result.err("Invalid Date instance");
122
- }
123
-
124
- return Result.ok(value);
49
+ private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
50
+ return "field" in node && "op" in node;
125
51
  }
126
52
 
127
- if (typeof value !== "string" || !ISO_8601_UTC_DATE_PATTERN.test(value)) {
128
- return null;
53
+ private static isAndNode(node: ConditionNode): node is ConditionAndNode {
54
+ return "and" in node;
129
55
  }
130
56
 
131
- const parsed = new Date(value);
132
- if (!PolicyDateUtils.isValid(parsed) || parsed.toISOString() !== value) {
133
- return Result.err("Invalid ISO 8601 UTC date string");
57
+ private static isOrNode(node: ConditionNode): node is ConditionOrNode {
58
+ return "or" in node;
134
59
  }
135
60
 
136
- return Result.ok(parsed);
137
- }
138
-
139
- private static evaluateRelationalNumbers(
140
- actual: number,
141
- op: RelationalOperator,
142
- expected: number,
143
- ): boolean {
144
- switch (op) {
145
- case "gt":
146
- return actual > expected;
147
- case "gte":
148
- return actual >= expected;
149
- case "lt":
150
- return actual < expected;
151
- case "lte":
152
- return actual <= expected;
61
+ private static isNotNode(node: ConditionNode): node is ConditionNotNode {
62
+ return "not" in node;
153
63
  }
154
- }
155
-
156
- private buildReport(params: {
157
- readonly level: ConditionEvaluationReport["level"];
158
- readonly tag: ConditionEvaluationReport["tag"];
159
- readonly message: string;
160
- readonly details: Readonly<Record<string, unknown>>;
161
- }): ConditionEvaluationReport {
162
- return {
163
- occurredAt: new Date(),
164
- level: params.level,
165
- tag: params.tag,
166
- message: params.message,
167
- engineVersion: this.options.engineVersion,
168
- details: params.details,
169
- };
170
- }
171
-
172
- private conditionEvalErr<TValue>(
173
- node: ConditionNode,
174
- error: unknown,
175
- ): Result<TValue, string> {
176
- const cause = ConditionEvaluatorV1.describeError(error);
177
- const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
178
-
179
- this.options.reporter.error(
180
- this.buildReport({
181
- level: "error",
182
- tag: CONDITION_EVAL_THROWN_TAG,
183
- message,
184
- details: { node, cause },
185
- }),
186
- );
187
-
188
- return Result.err(message);
189
- }
190
-
191
- private reportDateOperandError(
192
- node: ConditionLeafNode,
193
- actual: unknown,
194
- ): Result<boolean, string> {
195
- const message = ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
196
-
197
- this.options.reporter.error(
198
- this.buildReport({
199
- level: "error",
200
- tag: INVALID_DATE_OPERAND_TAG,
201
- message,
202
- details: {
203
- field: node.field,
204
- op: node.op,
205
- actual,
206
- expected: node.value,
207
- node,
208
- },
209
- }),
210
- );
211
-
212
- return Result.err(message);
213
- }
214
-
215
- private evaluateDateRelationalNode(
216
- node: ConditionLeafNode,
217
- actual: unknown,
218
- ): Result<boolean, string> | null {
219
- if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
220
- return null;
64
+
65
+ private static isRelationalOperator(
66
+ op: ConditionOp,
67
+ ): op is RelationalOperator {
68
+ return op === "gt" || op === "gte" || op === "lt" || op === "lte";
221
69
  }
222
70
 
223
- const actualDateResult = ConditionEvaluatorV1.parseComparableDate(actual);
224
- const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
225
- node.value,
226
- );
71
+ private static describeError(error: unknown): {
72
+ name: string;
73
+ message: string;
74
+ } {
75
+ if (error instanceof Error) {
76
+ return {
77
+ name: error.name,
78
+ message: error.message,
79
+ };
80
+ }
227
81
 
228
- if (actualDateResult === null && expectedDateResult === null) {
229
- return null;
82
+ return {
83
+ name: "NonErrorThrown",
84
+ message: String(error),
85
+ };
230
86
  }
231
87
 
232
- const allowsNull = node.allowNull === true;
233
-
234
- if (actual === null) {
235
- if (allowsNull) {
236
- return Result.ok(false);
237
- }
238
-
239
- const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
240
- node,
241
- actual,
242
- );
243
- this.options.reporter.error(
244
- this.buildReport({
245
- level: "error",
246
- tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
247
- message,
248
- details: {
249
- field: node.field,
250
- op: node.op,
251
- actual,
252
- expected: node.value,
253
- allowNull: allowsNull,
254
- node,
255
- },
256
- }),
257
- );
88
+ private static buildNullishNumericOperandMessage(
89
+ node: ConditionLeafNode,
90
+ actual: null | undefined,
91
+ ): string {
92
+ const resolvedValue = actual === null ? "null" : "undefined";
258
93
 
259
- return Result.err(message);
94
+ return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
260
95
  }
261
96
 
262
- if (actual === undefined) {
263
- const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
264
- node,
265
- actual,
266
- );
267
- this.options.reporter.error(
268
- this.buildReport({
269
- level: "error",
270
- tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
271
- message,
272
- details: {
273
- field: node.field,
274
- op: node.op,
275
- actual,
276
- expected: node.value,
277
- allowNull: allowsNull,
278
- node,
279
- },
280
- }),
281
- );
97
+ private static buildNullishDateOperandMessage(
98
+ node: ConditionLeafNode,
99
+ actual: null | undefined,
100
+ ): string {
101
+ const resolvedValue = actual === null ? "null" : "undefined";
282
102
 
283
- return Result.err(message);
103
+ return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
284
104
  }
285
105
 
286
- if (
287
- actualDateResult === null ||
288
- actualDateResult.isErr() ||
289
- expectedDateResult === null ||
290
- expectedDateResult.isErr()
291
- ) {
292
- return this.reportDateOperandError(node, actual);
106
+ private static buildInvalidDateOperandMessage(
107
+ node: ConditionLeafNode,
108
+ ): string {
109
+ return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
293
110
  }
294
111
 
295
- return Result.ok(
296
- ConditionEvaluatorV1.evaluateRelationalNumbers(
297
- actualDateResult.getOrNull()!.getTime(),
298
- node.op,
299
- expectedDateResult.getOrNull()!.getTime(),
300
- ),
301
- );
302
- }
303
-
304
- private evaluateOperator(
305
- actual: unknown,
306
- op: ConditionOp,
307
- expected: unknown,
308
- ): boolean {
309
- switch (op) {
310
- case "eq":
311
- return actual === expected;
312
- case "neq":
313
- return actual !== expected;
314
- case "gt":
315
- return (
316
- typeof actual === "number" &&
317
- typeof expected === "number" &&
318
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
319
- );
320
- case "gte":
321
- return (
322
- typeof actual === "number" &&
323
- typeof expected === "number" &&
324
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
325
- );
326
- case "lt":
327
- return (
328
- typeof actual === "number" &&
329
- typeof expected === "number" &&
330
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
331
- );
332
- case "lte":
333
- return (
334
- typeof actual === "number" &&
335
- typeof expected === "number" &&
336
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
337
- );
338
- case "in":
339
- return Array.isArray(expected) && expected.includes(actual);
340
- case "notIn":
341
- return Array.isArray(expected) && !expected.includes(actual);
342
- case "isNull":
343
- return actual === null;
344
- case "isNotNull":
345
- return actual !== null && actual !== undefined;
112
+ private static buildEmptyOrConditionMessage(): string {
113
+ return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
346
114
  }
347
- }
348
-
349
- private evaluateNumericRelationalNode(
350
- node: ConditionLeafNode,
351
- actual: unknown,
352
- ): Result<boolean, string> {
353
- const allowsNull = node.allowNull === true;
354
- if (actual === undefined || (actual === null && !allowsNull)) {
355
- const message = ConditionEvaluatorV1.buildNullishNumericOperandMessage(
356
- node,
357
- actual as null | undefined,
358
- );
359
-
360
- this.options.reporter.error(
361
- this.buildReport({
362
- level: "error",
363
- tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
364
- message,
365
- details: {
366
- field: node.field,
367
- op: node.op,
368
- actual,
369
- expected: node.value,
370
- allowNull: allowsNull,
371
- node,
372
- },
373
- }),
374
- );
375
115
 
376
- return Result.err(message);
377
- }
116
+ private static parseComparableDate(
117
+ value: unknown,
118
+ ): Result<Date, string> | null {
119
+ if (value instanceof Date) {
120
+ if (!PolicyDateUtils.isValid(value)) {
121
+ return Result.err("Invalid Date instance");
122
+ }
378
123
 
379
- return Result.ok(this.evaluateOperator(actual, node.op, node.value));
380
- }
381
-
382
- private evaluateLeafNode(node: ConditionLeafNode): Result<boolean, string> {
383
- const actualResult = PolicyContextPath.getOrAbsent(
384
- this.context,
385
- node.field,
386
- );
387
- if (actualResult.isErr()) {
388
- this.options.reporter.warn(
389
- this.buildReport({
390
- level: "warn",
391
- tag: "MISSING_CONTEXT_FIELD",
392
- message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
393
- details: { field: node.field, node },
394
- }),
395
- );
396
-
397
- return Result.err(
398
- `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
399
- );
400
- }
124
+ return Result.ok(value);
125
+ }
401
126
 
402
- try {
403
- const actual = actualResult.getOrThrow();
404
- if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
405
- const dateResult = this.evaluateDateRelationalNode(node, actual);
406
- if (dateResult !== null) {
407
- return dateResult;
127
+ if (
128
+ typeof value !== "string" ||
129
+ !ISO_8601_UTC_DATE_PATTERN.test(value)
130
+ ) {
131
+ return null;
408
132
  }
409
133
 
410
- return this.evaluateNumericRelationalNode(node, actual);
411
- }
134
+ const parsed = new Date(value);
135
+ if (
136
+ !PolicyDateUtils.isValid(parsed) ||
137
+ parsed.toISOString() !== value
138
+ ) {
139
+ return Result.err("Invalid ISO 8601 UTC date string");
140
+ }
412
141
 
413
- return Result.ok(this.evaluateOperator(actual, node.op, node.value));
414
- } catch (error) {
415
- return this.conditionEvalErr(node, error);
142
+ return Result.ok(parsed);
416
143
  }
417
- }
418
144
 
419
- private buildTrace(params: {
420
- readonly conditionPath: string;
421
- readonly decisiveNode: ConditionNode;
422
- readonly lastEvaluatedLeaf: ConditionLeafNode;
423
- readonly lastEvaluatedLeafPath: string;
424
- readonly lastEvaluatedLeafResult: boolean;
425
- }): ConditionEvaluationTrace {
426
- return {
427
- conditionPath: params.conditionPath,
428
- decisiveNode: params.decisiveNode,
429
- lastEvaluatedLeaf: params.lastEvaluatedLeaf,
430
- lastEvaluatedLeafPath: params.lastEvaluatedLeafPath,
431
- lastEvaluatedLeafResult: params.lastEvaluatedLeafResult,
432
- };
433
- }
434
-
435
- private evaluateWithTraceInternal(
436
- node: ConditionNode,
437
- path: string,
438
- ): Result<TracedConditionEvaluation, string> {
439
- if (ConditionEvaluatorV1.isLeafNode(node)) {
440
- const leafResult = this.evaluateLeafNode(node);
441
- if (leafResult.isErr()) {
442
- return Result.err(leafResult.errorOrNull()!);
443
- }
444
-
445
- const matched = leafResult.getOrNull()!;
446
- return Result.ok({
447
- matched,
448
- trace: this.buildTrace({
449
- conditionPath: path,
450
- decisiveNode: node,
451
- lastEvaluatedLeaf: node,
452
- lastEvaluatedLeafPath: path,
453
- lastEvaluatedLeafResult: matched,
454
- }),
455
- });
145
+ private static evaluateRelationalNumbers(
146
+ actual: number,
147
+ op: RelationalOperator,
148
+ expected: number,
149
+ ): boolean {
150
+ switch (op) {
151
+ case "gt":
152
+ return actual > expected;
153
+ case "gte":
154
+ return actual >= expected;
155
+ case "lt":
156
+ return actual < expected;
157
+ case "lte":
158
+ return actual <= expected;
159
+ }
456
160
  }
457
161
 
458
- if (ConditionEvaluatorV1.isAndNode(node)) {
459
- let lastTracedChild: TracedConditionEvaluation | null = null;
162
+ private buildReport(params: {
163
+ readonly level: ConditionEvaluationReport["level"];
164
+ readonly tag: ConditionEvaluationReport["tag"];
165
+ readonly message: string;
166
+ readonly details: Readonly<Record<string, unknown>>;
167
+ }): ConditionEvaluationReport {
168
+ return {
169
+ occurredAt: new Date(),
170
+ level: params.level,
171
+ tag: params.tag,
172
+ message: params.message,
173
+ engineVersion: this.options.engineVersion,
174
+ details: params.details,
175
+ };
176
+ }
460
177
 
461
- for (const [index, child] of node.and.entries()) {
462
- const childPath = `${path}.and[${index}]`;
463
- const childResult = this.evaluateWithTraceInternal(child, childPath);
464
- if (childResult.isErr()) {
465
- return Result.err(childResult.errorOrNull()!);
466
- }
178
+ private conditionEvalErr<TValue>(
179
+ node: ConditionNode,
180
+ error: unknown,
181
+ ): Result<TValue, string> {
182
+ const cause = ConditionEvaluatorV1.describeError(error);
183
+ const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
184
+
185
+ this.options.reporter.error(
186
+ this.buildReport({
187
+ level: "error",
188
+ tag: CONDITION_EVAL_THROWN_TAG,
189
+ message,
190
+ details: { node, cause },
191
+ }),
192
+ );
193
+
194
+ return Result.err(message);
195
+ }
467
196
 
468
- const tracedChild = childResult.getOrNull()!;
469
- lastTracedChild = tracedChild;
470
- if (!tracedChild.matched) {
471
- return Result.ok({
472
- matched: false,
473
- trace: this.buildTrace({
474
- conditionPath: childPath,
475
- decisiveNode: child,
476
- lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
477
- lastEvaluatedLeafPath: tracedChild.trace.lastEvaluatedLeafPath,
478
- lastEvaluatedLeafResult:
479
- tracedChild.trace.lastEvaluatedLeafResult,
197
+ private reportDateOperandError(
198
+ node: ConditionLeafNode,
199
+ actual: unknown,
200
+ ): Result<boolean, string> {
201
+ const message =
202
+ ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
203
+
204
+ this.options.reporter.error(
205
+ this.buildReport({
206
+ level: "error",
207
+ tag: INVALID_DATE_OPERAND_TAG,
208
+ message,
209
+ details: {
210
+ field: node.field,
211
+ op: node.op,
212
+ actual,
213
+ expected: node.value,
214
+ node,
215
+ },
480
216
  }),
481
- });
482
- }
483
- }
217
+ );
484
218
 
485
- return Result.ok({
486
- matched: true,
487
- trace: lastTracedChild!.trace,
488
- });
219
+ return Result.err(message);
489
220
  }
490
221
 
491
- if (ConditionEvaluatorV1.isOrNode(node)) {
492
- let lastTrace: ConditionEvaluationTrace | null = null;
222
+ private evaluateDateRelationalNode(
223
+ node: ConditionLeafNode,
224
+ actual: unknown,
225
+ ): Result<boolean, string> | null {
226
+ if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
227
+ return null;
228
+ }
493
229
 
494
- for (const [index, child] of node.or.entries()) {
495
- const childResult = this.evaluateWithTraceInternal(
496
- child,
497
- `${path}.or[${index}]`,
230
+ const actualDateResult =
231
+ ConditionEvaluatorV1.parseComparableDate(actual);
232
+ const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
233
+ node.value,
498
234
  );
499
- if (childResult.isErr()) {
500
- return Result.err(childResult.errorOrNull()!);
235
+
236
+ if (actualDateResult === null && expectedDateResult === null) {
237
+ return null;
501
238
  }
502
239
 
503
- const tracedChild = childResult.getOrNull()!;
504
- lastTrace = tracedChild.trace;
505
- if (tracedChild.matched) {
506
- return Result.ok({
507
- matched: true,
508
- trace: tracedChild.trace,
509
- });
240
+ const allowsNull = node.allowNull === true;
241
+
242
+ if (actual === null) {
243
+ if (allowsNull) {
244
+ return Result.ok(false);
245
+ }
246
+
247
+ const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
248
+ node,
249
+ actual,
250
+ );
251
+ this.options.reporter.error(
252
+ this.buildReport({
253
+ level: "error",
254
+ tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
255
+ message,
256
+ details: {
257
+ field: node.field,
258
+ op: node.op,
259
+ actual,
260
+ expected: node.value,
261
+ allowNull: allowsNull,
262
+ node,
263
+ },
264
+ }),
265
+ );
266
+
267
+ return Result.err(message);
510
268
  }
511
- }
512
269
 
513
- if (lastTrace === null) {
514
- return Result.err(ConditionEvaluatorV1.buildEmptyOrConditionMessage());
515
- }
270
+ if (actual === undefined) {
271
+ const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
272
+ node,
273
+ actual,
274
+ );
275
+ this.options.reporter.error(
276
+ this.buildReport({
277
+ level: "error",
278
+ tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
279
+ message,
280
+ details: {
281
+ field: node.field,
282
+ op: node.op,
283
+ actual,
284
+ expected: node.value,
285
+ allowNull: allowsNull,
286
+ node,
287
+ },
288
+ }),
289
+ );
290
+
291
+ return Result.err(message);
292
+ }
293
+
294
+ if (
295
+ actualDateResult === null ||
296
+ actualDateResult.isErr() ||
297
+ expectedDateResult === null ||
298
+ expectedDateResult.isErr()
299
+ ) {
300
+ return this.reportDateOperandError(node, actual);
301
+ }
516
302
 
517
- return Result.ok({ matched: false, trace: lastTrace! });
303
+ return Result.ok(
304
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
305
+ actualDateResult.getOrNull()!.getTime(),
306
+ node.op,
307
+ expectedDateResult.getOrNull()!.getTime(),
308
+ ),
309
+ );
518
310
  }
519
311
 
520
- if (ConditionEvaluatorV1.isNotNode(node)) {
521
- const childPath = `${path}.not`;
522
- const childResult = this.evaluateWithTraceInternal(node.not, childPath);
523
- if (childResult.isErr()) {
524
- return Result.err(childResult.errorOrNull()!);
525
- }
526
-
527
- const tracedChild = childResult.getOrNull()!;
528
- return Result.ok({
529
- matched: !tracedChild.matched,
530
- trace: this.buildTrace({
531
- conditionPath: path,
532
- decisiveNode: node,
533
- lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
534
- lastEvaluatedLeafPath: tracedChild.trace.lastEvaluatedLeafPath,
535
- lastEvaluatedLeafResult: tracedChild.trace.lastEvaluatedLeafResult,
536
- }),
537
- });
312
+ private evaluateOperator(
313
+ actual: unknown,
314
+ op: ConditionOp,
315
+ expected: unknown,
316
+ ): boolean {
317
+ switch (op) {
318
+ case "eq":
319
+ return actual === expected;
320
+ case "neq":
321
+ return actual !== expected;
322
+ case "gt":
323
+ return (
324
+ typeof actual === "number" &&
325
+ typeof expected === "number" &&
326
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
327
+ actual,
328
+ op,
329
+ expected,
330
+ )
331
+ );
332
+ case "gte":
333
+ return (
334
+ typeof actual === "number" &&
335
+ typeof expected === "number" &&
336
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
337
+ actual,
338
+ op,
339
+ expected,
340
+ )
341
+ );
342
+ case "lt":
343
+ return (
344
+ typeof actual === "number" &&
345
+ typeof expected === "number" &&
346
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
347
+ actual,
348
+ op,
349
+ expected,
350
+ )
351
+ );
352
+ case "lte":
353
+ return (
354
+ typeof actual === "number" &&
355
+ typeof expected === "number" &&
356
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
357
+ actual,
358
+ op,
359
+ expected,
360
+ )
361
+ );
362
+ case "in":
363
+ return Array.isArray(expected) && expected.includes(actual);
364
+ case "notIn":
365
+ return Array.isArray(expected) && !expected.includes(actual);
366
+ case "isNull":
367
+ return actual === null;
368
+ case "isNotNull":
369
+ return actual !== null && actual !== undefined;
370
+ }
538
371
  }
539
372
 
540
- return this.conditionEvalErr(
541
- node,
542
- new TypeError("Invalid condition node shape"),
543
- );
544
- }
545
-
546
- evaluate(node: ConditionNode): Result<boolean, string> {
547
- try {
548
- const result = this.evaluateWithTraceInternal(node, "$");
549
- if (result.isErr()) {
550
- return Result.err(result.errorOrNull()!);
551
- }
552
-
553
- return Result.ok(result.getOrNull()!.matched);
554
- } catch (error) {
555
- return this.conditionEvalErr(node, error);
373
+ private evaluateNumericRelationalNode(
374
+ node: ConditionLeafNode,
375
+ actual: unknown,
376
+ ): Result<boolean, string> {
377
+ const allowsNull = node.allowNull === true;
378
+ if (actual === undefined || (actual === null && !allowsNull)) {
379
+ const message =
380
+ ConditionEvaluatorV1.buildNullishNumericOperandMessage(
381
+ node,
382
+ actual as null | undefined,
383
+ );
384
+
385
+ this.options.reporter.error(
386
+ this.buildReport({
387
+ level: "error",
388
+ tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
389
+ message,
390
+ details: {
391
+ field: node.field,
392
+ op: node.op,
393
+ actual,
394
+ expected: node.value,
395
+ allowNull: allowsNull,
396
+ node,
397
+ },
398
+ }),
399
+ );
400
+
401
+ return Result.err(message);
402
+ }
403
+
404
+ return Result.ok(this.evaluateOperator(actual, node.op, node.value));
556
405
  }
557
- }
558
-
559
- evaluateWithTrace(
560
- node: ConditionNode,
561
- ): Result<TracedConditionEvaluation, string> {
562
- try {
563
- return this.evaluateWithTraceInternal(node, "$");
564
- } catch (error) {
565
- return this.conditionEvalErr(node, error);
406
+
407
+ private evaluateLeafNode(node: ConditionLeafNode): Result<boolean, string> {
408
+ const actualResult = PolicyContextPath.getOrAbsent(
409
+ this.context,
410
+ node.field,
411
+ );
412
+ if (actualResult.isErr()) {
413
+ this.options.reporter.warn(
414
+ this.buildReport({
415
+ level: "warn",
416
+ tag: "MISSING_CONTEXT_FIELD",
417
+ message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
418
+ details: { field: node.field, node },
419
+ }),
420
+ );
421
+
422
+ return Result.err(
423
+ `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
424
+ );
425
+ }
426
+
427
+ try {
428
+ const actual = actualResult.getOrThrow();
429
+ if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
430
+ const dateResult = this.evaluateDateRelationalNode(
431
+ node,
432
+ actual,
433
+ );
434
+ if (dateResult !== null) {
435
+ return dateResult;
436
+ }
437
+
438
+ return this.evaluateNumericRelationalNode(node, actual);
439
+ }
440
+
441
+ return Result.ok(
442
+ this.evaluateOperator(actual, node.op, node.value),
443
+ );
444
+ } catch (error) {
445
+ return this.conditionEvalErr(node, error);
446
+ }
566
447
  }
567
- }
568
448
 
569
- static extractFields(node: ConditionNode): string[] {
570
- if (ConditionEvaluatorV1.isLeafNode(node)) {
571
- return [node.field];
449
+ private buildTrace(params: {
450
+ readonly conditionPath: string;
451
+ readonly decisiveNode: ConditionNode;
452
+ readonly lastEvaluatedLeaf: ConditionLeafNode;
453
+ readonly lastEvaluatedLeafPath: string;
454
+ readonly lastEvaluatedLeafResult: boolean;
455
+ }): ConditionEvaluationTrace {
456
+ return {
457
+ conditionPath: params.conditionPath,
458
+ decisiveNode: params.decisiveNode,
459
+ lastEvaluatedLeaf: params.lastEvaluatedLeaf,
460
+ lastEvaluatedLeafPath: params.lastEvaluatedLeafPath,
461
+ lastEvaluatedLeafResult: params.lastEvaluatedLeafResult,
462
+ };
572
463
  }
573
464
 
574
- if (ConditionEvaluatorV1.isAndNode(node)) {
575
- return node.and.flatMap((child) =>
576
- ConditionEvaluatorV1.extractFields(child),
577
- );
465
+ private evaluateWithTraceInternal(
466
+ node: ConditionNode,
467
+ path: string,
468
+ ): Result<TracedConditionEvaluation, string> {
469
+ if (ConditionEvaluatorV1.isLeafNode(node)) {
470
+ const leafResult = this.evaluateLeafNode(node);
471
+ if (leafResult.isErr()) {
472
+ return Result.err(leafResult.errorOrNull()!);
473
+ }
474
+
475
+ const matched = leafResult.getOrNull()!;
476
+ return Result.ok({
477
+ matched,
478
+ trace: this.buildTrace({
479
+ conditionPath: path,
480
+ decisiveNode: node,
481
+ lastEvaluatedLeaf: node,
482
+ lastEvaluatedLeafPath: path,
483
+ lastEvaluatedLeafResult: matched,
484
+ }),
485
+ });
486
+ }
487
+
488
+ if (ConditionEvaluatorV1.isAndNode(node)) {
489
+ let lastTracedChild: TracedConditionEvaluation | null = null;
490
+
491
+ for (const [index, child] of node.and.entries()) {
492
+ const childPath = `${path}.and[${index}]`;
493
+ const childResult = this.evaluateWithTraceInternal(
494
+ child,
495
+ childPath,
496
+ );
497
+ if (childResult.isErr()) {
498
+ return Result.err(childResult.errorOrNull()!);
499
+ }
500
+
501
+ const tracedChild = childResult.getOrNull()!;
502
+ lastTracedChild = tracedChild;
503
+ if (!tracedChild.matched) {
504
+ return Result.ok({
505
+ matched: false,
506
+ trace: this.buildTrace({
507
+ conditionPath: childPath,
508
+ decisiveNode: child,
509
+ lastEvaluatedLeaf:
510
+ tracedChild.trace.lastEvaluatedLeaf,
511
+ lastEvaluatedLeafPath:
512
+ tracedChild.trace.lastEvaluatedLeafPath,
513
+ lastEvaluatedLeafResult:
514
+ tracedChild.trace.lastEvaluatedLeafResult,
515
+ }),
516
+ });
517
+ }
518
+ }
519
+
520
+ return Result.ok({
521
+ matched: true,
522
+ trace: lastTracedChild!.trace,
523
+ });
524
+ }
525
+
526
+ if (ConditionEvaluatorV1.isOrNode(node)) {
527
+ let lastTrace: ConditionEvaluationTrace | null = null;
528
+
529
+ for (const [index, child] of node.or.entries()) {
530
+ const childResult = this.evaluateWithTraceInternal(
531
+ child,
532
+ `${path}.or[${index}]`,
533
+ );
534
+ if (childResult.isErr()) {
535
+ return Result.err(childResult.errorOrNull()!);
536
+ }
537
+
538
+ const tracedChild = childResult.getOrNull()!;
539
+ lastTrace = tracedChild.trace;
540
+ if (tracedChild.matched) {
541
+ return Result.ok({
542
+ matched: true,
543
+ trace: tracedChild.trace,
544
+ });
545
+ }
546
+ }
547
+
548
+ if (lastTrace === null) {
549
+ return Result.err(
550
+ ConditionEvaluatorV1.buildEmptyOrConditionMessage(),
551
+ );
552
+ }
553
+
554
+ return Result.ok({ matched: false, trace: lastTrace! });
555
+ }
556
+
557
+ if (ConditionEvaluatorV1.isNotNode(node)) {
558
+ const childPath = `${path}.not`;
559
+ const childResult = this.evaluateWithTraceInternal(
560
+ node.not,
561
+ childPath,
562
+ );
563
+ if (childResult.isErr()) {
564
+ return Result.err(childResult.errorOrNull()!);
565
+ }
566
+
567
+ const tracedChild = childResult.getOrNull()!;
568
+ return Result.ok({
569
+ matched: !tracedChild.matched,
570
+ trace: this.buildTrace({
571
+ conditionPath: path,
572
+ decisiveNode: node,
573
+ lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
574
+ lastEvaluatedLeafPath:
575
+ tracedChild.trace.lastEvaluatedLeafPath,
576
+ lastEvaluatedLeafResult:
577
+ tracedChild.trace.lastEvaluatedLeafResult,
578
+ }),
579
+ });
580
+ }
581
+
582
+ return this.conditionEvalErr(
583
+ node,
584
+ new TypeError("Invalid condition node shape"),
585
+ );
578
586
  }
579
587
 
580
- if (ConditionEvaluatorV1.isOrNode(node)) {
581
- return node.or.flatMap((child) =>
582
- ConditionEvaluatorV1.extractFields(child),
583
- );
588
+ evaluate(node: ConditionNode): Result<boolean, string> {
589
+ try {
590
+ const result = this.evaluateWithTraceInternal(node, "$");
591
+ if (result.isErr()) {
592
+ return Result.err(result.errorOrNull()!);
593
+ }
594
+
595
+ return Result.ok(result.getOrNull()!.matched);
596
+ } catch (error) {
597
+ return this.conditionEvalErr(node, error);
598
+ }
584
599
  }
585
600
 
586
- if (ConditionEvaluatorV1.isNotNode(node)) {
587
- return ConditionEvaluatorV1.extractFields(node.not);
601
+ evaluateWithTrace(
602
+ node: ConditionNode,
603
+ ): Result<TracedConditionEvaluation, string> {
604
+ try {
605
+ return this.evaluateWithTraceInternal(node, "$");
606
+ } catch (error) {
607
+ return this.conditionEvalErr(node, error);
608
+ }
588
609
  }
589
610
 
590
- return [];
591
- }
611
+ static extractFields(node: ConditionNode): string[] {
612
+ if (ConditionEvaluatorV1.isLeafNode(node)) {
613
+ return [node.field];
614
+ }
615
+
616
+ if (ConditionEvaluatorV1.isAndNode(node)) {
617
+ return node.and.flatMap((child) =>
618
+ ConditionEvaluatorV1.extractFields(child),
619
+ );
620
+ }
621
+
622
+ if (ConditionEvaluatorV1.isOrNode(node)) {
623
+ return node.or.flatMap((child) =>
624
+ ConditionEvaluatorV1.extractFields(child),
625
+ );
626
+ }
627
+
628
+ if (ConditionEvaluatorV1.isNotNode(node)) {
629
+ return ConditionEvaluatorV1.extractFields(node.not);
630
+ }
631
+
632
+ return [];
633
+ }
592
634
  }