@cullet/erp-core 1.0.2 → 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 -67
  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,17 +3,17 @@ import { type PolicyCatalog, PolicyCatalogEntry, PolicyKey } from "../catalog";
3
3
  import type { ContextSeed, PolicyContextBuilder } from "../context";
4
4
  import { ContextSeedValidator } from "../context";
5
5
  import {
6
- PolicyDefinition,
7
- type PolicyDefinitionRepository,
8
- type PolicyScope,
6
+ PolicyDefinition,
7
+ type PolicyDefinitionRepository,
8
+ type PolicyScope,
9
9
  } from "../defs";
10
10
  import type { PolicyDecisionId, PolicyDefinitionId } from "../policy-ids";
11
11
  import {
12
- type ComputeOutcome,
13
- ComputeRegistry,
14
- GateEngineRegistry,
15
- type GateOutcome,
16
- type PolicyContext,
12
+ type ComputeOutcome,
13
+ ComputeRegistry,
14
+ GateEngineRegistry,
15
+ type GateOutcome,
16
+ type PolicyContext,
17
17
  } from "../engines";
18
18
  import { PolicyResolver } from "../resolver";
19
19
  import { Result } from "../../result/result";
@@ -23,33 +23,33 @@ import type { PolicyEvent, PolicyReporter } from "../../config/policy-reporter";
23
23
  import { SilentPolicyReporter } from "../../config/silent-policy-reporter";
24
24
 
25
25
  import {
26
- PolicyEvaluationErrors,
27
- type PolicyEvaluationError,
26
+ PolicyEvaluationErrors,
27
+ type PolicyEvaluationError,
28
28
  } from "./policy-evaluation-error";
29
29
 
30
30
  // ─── Input ──────────────────────────────────────────────────────────────────
31
31
 
32
32
  export interface EvaluateInput {
33
- readonly decisionId: PolicyDecisionId;
34
- readonly policyKey: string;
35
- readonly scopeChain: readonly PolicyScope[];
36
- readonly contextVersion: number;
37
- readonly seed: ContextSeed;
33
+ readonly decisionId: PolicyDecisionId;
34
+ readonly policyKey: string;
35
+ readonly scopeChain: readonly PolicyScope[];
36
+ readonly contextVersion: number;
37
+ readonly seed: ContextSeed;
38
38
  }
39
39
 
40
40
  export interface PolicyServiceOptions {
41
- readonly asOf?: DeriveAsOfOptions;
42
- readonly reporter?: PolicyReporter;
41
+ readonly asOf?: DeriveAsOfOptions;
42
+ readonly reporter?: PolicyReporter;
43
43
  }
44
44
 
45
45
  export interface PolicyServiceParams {
46
- readonly catalog: PolicyCatalog;
47
- readonly contextBuilder: PolicyContextBuilder;
48
- readonly defRepo: PolicyDefinitionRepository;
49
- readonly resolver: PolicyResolver;
50
- readonly gateEngines: GateEngineRegistry;
51
- readonly computeRegistry: ComputeRegistry;
52
- readonly options?: PolicyServiceOptions;
46
+ readonly catalog: PolicyCatalog;
47
+ readonly contextBuilder: PolicyContextBuilder;
48
+ readonly defRepo: PolicyDefinitionRepository;
49
+ readonly resolver: PolicyResolver;
50
+ readonly gateEngines: GateEngineRegistry;
51
+ readonly computeRegistry: ComputeRegistry;
52
+ readonly options?: PolicyServiceOptions;
53
53
  }
54
54
 
55
55
  // ─── Output ─────────────────────────────────────────────────────────────────
@@ -60,431 +60,444 @@ export type PolicyDecision = GateOutcome | ComputeOutcome;
60
60
  export type { PolicyEvaluationError } from "./policy-evaluation-error";
61
61
 
62
62
  export interface PolicyEvaluationResult {
63
- readonly decisionId: PolicyDecisionId;
64
- readonly ref: {
65
- readonly definitionId: PolicyDefinitionId;
66
- readonly policyKey: string;
67
- readonly policyVersion: string;
68
- readonly payloadHash: string;
69
- readonly gateEngineVersion?: number;
70
- readonly computeEngineVersion?: number;
71
- };
72
- readonly kind: "GATE" | "COMPUTE";
73
- readonly asOf: Date;
74
- readonly evaluatedAt: Date;
75
- readonly contextVersion: number;
76
- /** Business decision expressed as an Outcome — not a technical Result. */
77
- readonly decision: PolicyDecision;
63
+ readonly decisionId: PolicyDecisionId;
64
+ readonly ref: {
65
+ readonly definitionId: PolicyDefinitionId;
66
+ readonly policyKey: string;
67
+ readonly policyVersion: string;
68
+ readonly payloadHash: string;
69
+ readonly gateEngineVersion?: number;
70
+ readonly computeEngineVersion?: number;
71
+ };
72
+ readonly kind: "GATE" | "COMPUTE";
73
+ readonly asOf: Date;
74
+ readonly evaluatedAt: Date;
75
+ readonly contextVersion: number;
76
+ /** Business decision expressed as an Outcome — not a technical Result. */
77
+ readonly decision: PolicyDecision;
78
78
  }
79
79
 
80
80
  interface ResolvedPolicyCandidate {
81
- readonly definition: PolicyDefinition;
82
- readonly catalogEntry: PolicyCatalogEntry;
81
+ readonly definition: PolicyDefinition;
82
+ readonly catalogEntry: PolicyCatalogEntry;
83
83
  }
84
84
 
85
85
  // ─── Service ────────────────────────────────────────────────────────────────
86
86
 
87
87
  export class PolicyService {
88
- readonly #reporter: PolicyReporter;
89
- private readonly catalog: PolicyCatalog;
90
- private readonly contextBuilder: PolicyContextBuilder;
91
- private readonly defRepo: PolicyDefinitionRepository;
92
- private readonly resolver: PolicyResolver;
93
- private readonly gateEngines: GateEngineRegistry;
94
- private readonly computeRegistry: ComputeRegistry;
95
- private readonly options: PolicyServiceOptions;
96
-
97
- constructor(params: PolicyServiceParams) {
98
- this.catalog = params.catalog;
99
- this.contextBuilder = params.contextBuilder;
100
- this.defRepo = params.defRepo;
101
- this.resolver = params.resolver;
102
- this.gateEngines = params.gateEngines;
103
- this.computeRegistry = params.computeRegistry;
104
- this.options = params.options ?? {};
105
- this.#reporter = this.options.reporter ?? new SilentPolicyReporter();
106
- }
107
-
108
- #reportSafely(event: PolicyEvent): void {
109
- try {
110
- this.#reporter.report(event);
111
- } catch {
112
- // Falhas de telemetria nao podem interromper a avaliacao de politicas.
88
+ readonly #reporter: PolicyReporter;
89
+ private readonly catalog: PolicyCatalog;
90
+ private readonly contextBuilder: PolicyContextBuilder;
91
+ private readonly defRepo: PolicyDefinitionRepository;
92
+ private readonly resolver: PolicyResolver;
93
+ private readonly gateEngines: GateEngineRegistry;
94
+ private readonly computeRegistry: ComputeRegistry;
95
+ private readonly options: PolicyServiceOptions;
96
+
97
+ constructor(params: PolicyServiceParams) {
98
+ this.catalog = params.catalog;
99
+ this.contextBuilder = params.contextBuilder;
100
+ this.defRepo = params.defRepo;
101
+ this.resolver = params.resolver;
102
+ this.gateEngines = params.gateEngines;
103
+ this.computeRegistry = params.computeRegistry;
104
+ this.options = params.options ?? {};
105
+ this.#reporter = this.options.reporter ?? new SilentPolicyReporter();
113
106
  }
114
- }
115
107
 
116
- private resolveEvaluationNow(seed: ContextSeed): Result<Date, string> {
117
- const candidate = seed.fields["now"];
118
-
119
- if (candidate === undefined) {
120
- return Result.ok(new Date());
108
+ #reportSafely(event: PolicyEvent): void {
109
+ try {
110
+ this.#reporter.report(event);
111
+ } catch {
112
+ // Falhas de telemetria nao podem interromper a avaliacao de politicas.
113
+ }
121
114
  }
122
115
 
123
- if (!isValidDate(candidate)) {
124
- return Result.err("seed.fields.now must be a valid Date when provided");
125
- }
116
+ private resolveEvaluationNow(seed: ContextSeed): Result<Date, string> {
117
+ const candidate = seed.fields["now"];
126
118
 
127
- return Result.ok(candidate);
128
- }
129
-
130
- async evaluate(
131
- input: EvaluateInput,
132
- ): Promise<Result<PolicyEvaluationResult, PolicyEvaluationError>> {
133
- const result = await this.#evaluate(input);
134
- if (result.isErr()) {
135
- const error = result.errorOrNull()!;
136
- this.#reportSafely({
137
- kind: "policy-evaluation-failed",
138
- level: "error",
139
- policyKey: "policyKey" in error ? error.policyKey : undefined,
140
- errorKind: error.kind,
141
- message: error.message,
142
- cause: error.cause,
143
- occurredAt: new Date(),
144
- });
145
- } else {
146
- const ok = result.getOrNull()!;
147
- this.#reportSafely({
148
- kind: "policy-evaluation-completed",
149
- level: "info",
150
- policyKey: ok.ref.policyKey,
151
- decisionId: ok.decisionId,
152
- engineKind: ok.kind,
153
- occurredAt: ok.evaluatedAt,
154
- });
155
- }
156
- return result;
157
- }
158
-
159
- async #evaluate(
160
- input: EvaluateInput,
161
- ): Promise<Result<PolicyEvaluationResult, PolicyEvaluationError>> {
162
- const seedResult = ContextSeedValidator.validate(input.seed);
163
- if (seedResult.isErr()) {
164
- return Result.err(
165
- PolicyEvaluationErrors.invalidContext(
166
- "SEED_VALIDATION",
167
- input.policyKey,
168
- seedResult.errorOrNull()!,
169
- ),
170
- );
171
- }
172
- const seed = seedResult.getOrNull()!;
119
+ if (candidate === undefined) {
120
+ return Result.ok(new Date());
121
+ }
173
122
 
174
- const catalogFamilyResult = this.#resolveCatalogFamily(input.policyKey);
175
- if (catalogFamilyResult.isErr()) {
176
- return Result.err(catalogFamilyResult.errorOrNull()!);
177
- }
178
- const { key: policyKey, family } = catalogFamilyResult.getOrNull()!;
179
- const familyEntry = family[0]!;
180
-
181
- const nowResult = this.resolveEvaluationNow(seed);
182
- if (nowResult.isErr()) {
183
- return Result.err(
184
- PolicyEvaluationErrors.invalidContext(
185
- "AS_OF_DERIVATION",
186
- policyKey,
187
- nowResult.errorOrNull()!,
188
- ),
189
- );
190
- }
191
- const now = nowResult.getOrNull()!;
192
-
193
- const asOfResult = PolicyAsOfResolver.derive(
194
- familyEntry.asOfSource,
195
- seed,
196
- now,
197
- this.options.asOf,
198
- );
199
- if (asOfResult.isErr()) {
200
- return Result.err(
201
- PolicyEvaluationErrors.invalidContext(
202
- "AS_OF_DERIVATION",
203
- policyKey,
204
- asOfResult.errorOrNull()!,
205
- ),
206
- );
207
- }
208
- const asOf = asOfResult.getOrNull()!;
209
-
210
- const definitionResult = this.#findCandidates(
211
- policyKey,
212
- familyEntry.kind,
213
- asOf,
214
- input,
215
- );
216
- if (definitionResult.isErr()) {
217
- return Result.err(definitionResult.errorOrNull()!);
218
- }
219
- const {
220
- definition: policyDefinition,
221
- catalogEntry: versionedCatalogEntry,
222
- } = definitionResult.getOrNull()!;
223
-
224
- this.#reportSafely({
225
- kind: "policy-resolution",
226
- level: "info",
227
- policyKey,
228
- definitionId: policyDefinition.id,
229
- policyVersion: policyDefinition.policyVersion,
230
- engineKind: policyDefinition.isGate() ? "GATE" : "COMPUTE",
231
- engineVersion: policyDefinition.isGate()
232
- ? policyDefinition.gateEngineVersion!
233
- : policyDefinition.computeEngineVersion!,
234
- occurredAt: now,
235
- });
236
-
237
- const ctxResult = await this.contextBuilder.build(
238
- versionedCatalogEntry.contextRequirements,
239
- seed,
240
- );
241
- if (ctxResult.isErr()) {
242
- return Result.err(
243
- PolicyEvaluationErrors.invalidContext(
244
- "CONTEXT_BUILD",
245
- policyKey,
246
- ctxResult.errorOrNull()!,
247
- ),
248
- );
249
- }
250
- const context = ctxResult.getOrNull()!;
251
-
252
- const decisionResult = this.#executeEngine(
253
- policyKey,
254
- policyDefinition,
255
- context,
256
- );
257
- if (decisionResult.isErr()) {
258
- return Result.err(decisionResult.errorOrNull()!);
259
- }
260
- const decision = decisionResult.getOrNull()!;
261
-
262
- const result: PolicyEvaluationResult = {
263
- decisionId: input.decisionId,
264
- ref: {
265
- definitionId: policyDefinition.id,
266
- policyKey: policyDefinition.policyKey,
267
- policyVersion: policyDefinition.policyVersion,
268
- payloadHash: policyDefinition.payloadHash,
269
- ...(policyDefinition.isGate()
270
- ? {
271
- gateEngineVersion: policyDefinition.gateEngineVersion,
272
- }
273
- : {
274
- computeEngineVersion: policyDefinition.computeEngineVersion,
275
- }),
276
- },
277
- kind: policyDefinition.kind,
278
- asOf,
279
- evaluatedAt: now,
280
- contextVersion: input.contextVersion,
281
- decision,
282
- };
123
+ if (!isValidDate(candidate)) {
124
+ return Result.err(
125
+ "seed.fields.now must be a valid Date when provided",
126
+ );
127
+ }
283
128
 
284
- return Result.ok(result);
285
- }
286
-
287
- #resolveCatalogVariant(
288
- definition: PolicyDefinition,
289
- ): Result<PolicyCatalogEntry, string> {
290
- return this.catalog.getVersioned({
291
- key: definition.policyKey,
292
- kind: definition.kind,
293
- ...(definition.isGate()
294
- ? { gateEngineVersion: definition.gateEngineVersion }
295
- : {
296
- computeEngineVersion: definition.computeEngineVersion,
297
- }),
298
- payloadSchemaVersion: definition.payloadSchemaVersion,
299
- });
300
- }
301
-
302
- #resolveCatalogFamily(
303
- rawKey: string,
304
- ): Result<
305
- { key: string; family: readonly PolicyCatalogEntry[] },
306
- PolicyEvaluationError
307
- > {
308
- const keyResult = PolicyKey.parse(rawKey);
309
- if (keyResult.isErr()) {
310
- return Result.err(
311
- PolicyEvaluationErrors.invalidPolicyKey(
312
- rawKey,
313
- keyResult.errorOrNull()!,
314
- ),
315
- );
129
+ return Result.ok(candidate);
316
130
  }
317
- const policyKey = keyResult.getOrNull()!.toString();
318
-
319
- const catalogFamilyResult = this.catalog.getFamily(policyKey);
320
- if (catalogFamilyResult.isErr()) {
321
- return Result.err(
322
- PolicyEvaluationErrors.policyNotFound(
323
- policyKey,
324
- catalogFamilyResult.errorOrNull()!,
325
- ),
326
- );
131
+
132
+ async evaluate(
133
+ input: EvaluateInput,
134
+ ): Promise<Result<PolicyEvaluationResult, PolicyEvaluationError>> {
135
+ const result = await this.#evaluate(input);
136
+ if (result.isErr()) {
137
+ const error = result.errorOrNull()!;
138
+ this.#reportSafely({
139
+ kind: "policy-evaluation-failed",
140
+ level: "error",
141
+ policyKey: "policyKey" in error ? error.policyKey : undefined,
142
+ errorKind: error.kind,
143
+ message: error.message,
144
+ cause: error.cause,
145
+ occurredAt: new Date(),
146
+ });
147
+ } else {
148
+ const ok = result.getOrNull()!;
149
+ this.#reportSafely({
150
+ kind: "policy-evaluation-completed",
151
+ level: "info",
152
+ policyKey: ok.ref.policyKey,
153
+ decisionId: ok.decisionId,
154
+ engineKind: ok.kind,
155
+ occurredAt: ok.evaluatedAt,
156
+ });
157
+ }
158
+ return result;
327
159
  }
328
160
 
329
- return Result.ok({
330
- key: policyKey,
331
- family: catalogFamilyResult.getOrNull()!,
332
- });
333
- }
334
-
335
- #findCandidates(
336
- policyKey: string,
337
- policyKind: PolicyCatalogEntry["kind"],
338
- asOf: Date,
339
- input: EvaluateInput,
340
- ): Result<ResolvedPolicyCandidate, PolicyEvaluationError> {
341
- const candidates = this.defRepo.findCandidates({
342
- policyKey: policyKey,
343
- kind: policyKind,
344
- asOf,
345
- contextVersion: input.contextVersion,
346
- scopeChain: input.scopeChain,
347
- });
348
-
349
- const compatibleCandidates: ResolvedPolicyCandidate[] = [];
350
- const compatibleCandidatesByDefinitionId = new Map<
351
- string,
352
- ResolvedPolicyCandidate
353
- >();
354
- const incompatibleVariantErrorsByDefinitionId = new Map<string, string>();
355
-
356
- for (const candidate of candidates) {
357
- const compatibilityResult = this.#resolveCatalogVariant(candidate);
358
- if (compatibilityResult.isErr()) {
359
- incompatibleVariantErrorsByDefinitionId.set(
360
- candidate.id,
361
- compatibilityResult.errorOrNull()!,
161
+ async #evaluate(
162
+ input: EvaluateInput,
163
+ ): Promise<Result<PolicyEvaluationResult, PolicyEvaluationError>> {
164
+ const seedResult = ContextSeedValidator.validate(input.seed);
165
+ if (seedResult.isErr()) {
166
+ return Result.err(
167
+ PolicyEvaluationErrors.invalidContext(
168
+ "SEED_VALIDATION",
169
+ input.policyKey,
170
+ seedResult.errorOrNull()!,
171
+ ),
172
+ );
173
+ }
174
+ const seed = seedResult.getOrNull()!;
175
+
176
+ const catalogFamilyResult = this.#resolveCatalogFamily(input.policyKey);
177
+ if (catalogFamilyResult.isErr()) {
178
+ return Result.err(catalogFamilyResult.errorOrNull()!);
179
+ }
180
+ const { key: policyKey, family } = catalogFamilyResult.getOrNull()!;
181
+ const familyEntry = family[0]!;
182
+
183
+ const nowResult = this.resolveEvaluationNow(seed);
184
+ if (nowResult.isErr()) {
185
+ return Result.err(
186
+ PolicyEvaluationErrors.invalidContext(
187
+ "AS_OF_DERIVATION",
188
+ policyKey,
189
+ nowResult.errorOrNull()!,
190
+ ),
191
+ );
192
+ }
193
+ const now = nowResult.getOrNull()!;
194
+
195
+ const asOfResult = PolicyAsOfResolver.derive(
196
+ familyEntry.asOfSource,
197
+ seed,
198
+ now,
199
+ this.options.asOf,
362
200
  );
363
- continue;
364
- }
365
-
366
- const resolvedCandidate: ResolvedPolicyCandidate = {
367
- definition: candidate,
368
- catalogEntry: compatibilityResult.getOrNull()!,
369
- };
370
- compatibleCandidates.push(resolvedCandidate);
371
- compatibleCandidatesByDefinitionId.set(candidate.id, resolvedCandidate);
201
+ if (asOfResult.isErr()) {
202
+ return Result.err(
203
+ PolicyEvaluationErrors.invalidContext(
204
+ "AS_OF_DERIVATION",
205
+ policyKey,
206
+ asOfResult.errorOrNull()!,
207
+ ),
208
+ );
209
+ }
210
+ const asOf = asOfResult.getOrNull()!;
211
+
212
+ const definitionResult = this.#findCandidates(
213
+ policyKey,
214
+ familyEntry.kind,
215
+ asOf,
216
+ input,
217
+ );
218
+ if (definitionResult.isErr()) {
219
+ return Result.err(definitionResult.errorOrNull()!);
220
+ }
221
+ const {
222
+ definition: policyDefinition,
223
+ catalogEntry: versionedCatalogEntry,
224
+ } = definitionResult.getOrNull()!;
225
+
226
+ this.#reportSafely({
227
+ kind: "policy-resolution",
228
+ level: "info",
229
+ policyKey,
230
+ definitionId: policyDefinition.id,
231
+ policyVersion: policyDefinition.policyVersion,
232
+ engineKind: policyDefinition.isGate() ? "GATE" : "COMPUTE",
233
+ engineVersion: policyDefinition.isGate()
234
+ ? policyDefinition.gateEngineVersion!
235
+ : policyDefinition.computeEngineVersion!,
236
+ occurredAt: now,
237
+ });
238
+
239
+ const ctxResult = await this.contextBuilder.build(
240
+ versionedCatalogEntry.contextRequirements,
241
+ seed,
242
+ );
243
+ if (ctxResult.isErr()) {
244
+ return Result.err(
245
+ PolicyEvaluationErrors.invalidContext(
246
+ "CONTEXT_BUILD",
247
+ policyKey,
248
+ ctxResult.errorOrNull()!,
249
+ ),
250
+ );
251
+ }
252
+ const context = ctxResult.getOrNull()!;
253
+
254
+ const decisionResult = this.#executeEngine(
255
+ policyKey,
256
+ policyDefinition,
257
+ context,
258
+ );
259
+ if (decisionResult.isErr()) {
260
+ return Result.err(decisionResult.errorOrNull()!);
261
+ }
262
+ const decision = decisionResult.getOrNull()!;
263
+
264
+ const result: PolicyEvaluationResult = {
265
+ decisionId: input.decisionId,
266
+ ref: {
267
+ definitionId: policyDefinition.id,
268
+ policyKey: policyDefinition.policyKey,
269
+ policyVersion: policyDefinition.policyVersion,
270
+ payloadHash: policyDefinition.payloadHash,
271
+ ...(policyDefinition.isGate()
272
+ ? {
273
+ gateEngineVersion: policyDefinition.gateEngineVersion,
274
+ }
275
+ : {
276
+ computeEngineVersion:
277
+ policyDefinition.computeEngineVersion,
278
+ }),
279
+ },
280
+ kind: policyDefinition.kind,
281
+ asOf,
282
+ evaluatedAt: now,
283
+ contextVersion: input.contextVersion,
284
+ decision,
285
+ };
286
+
287
+ return Result.ok(result);
372
288
  }
373
289
 
374
- if (compatibleCandidates.length === 0 && candidates.length > 0) {
375
- const selectedCandidateResult = this.resolver.resolveBest(candidates);
376
- if (selectedCandidateResult.isOk()) {
377
- const selectedCandidate = selectedCandidateResult.getOrNull()!;
290
+ #resolveCatalogVariant(
291
+ definition: PolicyDefinition,
292
+ ): Result<PolicyCatalogEntry, string> {
293
+ return this.catalog.getVersioned({
294
+ key: definition.policyKey,
295
+ kind: definition.kind,
296
+ ...(definition.isGate()
297
+ ? { gateEngineVersion: definition.gateEngineVersion }
298
+ : {
299
+ computeEngineVersion: definition.computeEngineVersion,
300
+ }),
301
+ payloadSchemaVersion: definition.payloadSchemaVersion,
302
+ });
303
+ }
378
304
 
379
- return Result.err(
380
- PolicyEvaluationErrors.policyVariantNotFound({
305
+ #resolveCatalogFamily(
306
+ rawKey: string,
307
+ ): Result<
308
+ { key: string; family: readonly PolicyCatalogEntry[] },
309
+ PolicyEvaluationError
310
+ > {
311
+ const keyResult = PolicyKey.parse(rawKey);
312
+ if (keyResult.isErr()) {
313
+ return Result.err(
314
+ PolicyEvaluationErrors.invalidPolicyKey(
315
+ rawKey,
316
+ keyResult.errorOrNull()!,
317
+ ),
318
+ );
319
+ }
320
+ const policyKey = keyResult.getOrNull()!.toString();
321
+
322
+ const catalogFamilyResult = this.catalog.getFamily(policyKey);
323
+ if (catalogFamilyResult.isErr()) {
324
+ return Result.err(
325
+ PolicyEvaluationErrors.policyNotFound(
326
+ policyKey,
327
+ catalogFamilyResult.errorOrNull()!,
328
+ ),
329
+ );
330
+ }
331
+
332
+ return Result.ok({
333
+ key: policyKey,
334
+ family: catalogFamilyResult.getOrNull()!,
335
+ });
336
+ }
337
+
338
+ #findCandidates(
339
+ policyKey: string,
340
+ policyKind: PolicyCatalogEntry["kind"],
341
+ asOf: Date,
342
+ input: EvaluateInput,
343
+ ): Result<ResolvedPolicyCandidate, PolicyEvaluationError> {
344
+ const candidates = this.defRepo.findCandidates({
381
345
  policyKey: policyKey,
382
- policyKind: selectedCandidate.kind,
383
- payloadSchemaVersion: selectedCandidate.payloadSchemaVersion,
384
- ...(selectedCandidate.isGate()
385
- ? {
386
- gateEngineVersion: selectedCandidate.gateEngineVersion,
387
- }
388
- : {
389
- computeEngineVersion: selectedCandidate.computeEngineVersion,
390
- }),
391
- cause:
392
- incompatibleVariantErrorsByDefinitionId.get(
393
- selectedCandidate.id,
394
- ) ??
395
- "No compatible catalog variant matched the selected policy definition",
396
- }),
346
+ kind: policyKind,
347
+ asOf,
348
+ contextVersion: input.contextVersion,
349
+ scopeChain: input.scopeChain,
350
+ });
351
+
352
+ const compatibleCandidates: ResolvedPolicyCandidate[] = [];
353
+ const compatibleCandidatesByDefinitionId = new Map<
354
+ string,
355
+ ResolvedPolicyCandidate
356
+ >();
357
+ const incompatibleVariantErrorsByDefinitionId = new Map<
358
+ string,
359
+ string
360
+ >();
361
+
362
+ for (const candidate of candidates) {
363
+ const compatibilityResult = this.#resolveCatalogVariant(candidate);
364
+ if (compatibilityResult.isErr()) {
365
+ incompatibleVariantErrorsByDefinitionId.set(
366
+ candidate.id,
367
+ compatibilityResult.errorOrNull()!,
368
+ );
369
+ continue;
370
+ }
371
+
372
+ const resolvedCandidate: ResolvedPolicyCandidate = {
373
+ definition: candidate,
374
+ catalogEntry: compatibilityResult.getOrNull()!,
375
+ };
376
+ compatibleCandidates.push(resolvedCandidate);
377
+ compatibleCandidatesByDefinitionId.set(
378
+ candidate.id,
379
+ resolvedCandidate,
380
+ );
381
+ }
382
+
383
+ if (compatibleCandidates.length === 0 && candidates.length > 0) {
384
+ const selectedCandidateResult =
385
+ this.resolver.resolveBest(candidates);
386
+ if (selectedCandidateResult.isOk()) {
387
+ const selectedCandidate = selectedCandidateResult.getOrNull()!;
388
+
389
+ return Result.err(
390
+ PolicyEvaluationErrors.policyVariantNotFound({
391
+ policyKey: policyKey,
392
+ policyKind: selectedCandidate.kind,
393
+ payloadSchemaVersion:
394
+ selectedCandidate.payloadSchemaVersion,
395
+ ...(selectedCandidate.isGate()
396
+ ? {
397
+ gateEngineVersion:
398
+ selectedCandidate.gateEngineVersion,
399
+ }
400
+ : {
401
+ computeEngineVersion:
402
+ selectedCandidate.computeEngineVersion,
403
+ }),
404
+ cause:
405
+ incompatibleVariantErrorsByDefinitionId.get(
406
+ selectedCandidate.id,
407
+ ) ??
408
+ "No compatible catalog variant matched the selected policy definition",
409
+ }),
410
+ );
411
+ }
412
+ }
413
+
414
+ const bestResult = this.resolver.resolveBest(
415
+ compatibleCandidates.map((candidate) => candidate.definition),
416
+ );
417
+ if (bestResult.isErr()) {
418
+ return Result.err(
419
+ PolicyEvaluationErrors.policyDefinitionNotFound(
420
+ policyKey,
421
+ asOf,
422
+ input.contextVersion,
423
+ bestResult.errorOrNull()!,
424
+ ),
425
+ );
426
+ }
427
+
428
+ const bestDefinition = bestResult.getOrNull()!;
429
+ const resolvedCandidate = compatibleCandidatesByDefinitionId.get(
430
+ bestDefinition.id,
397
431
  );
398
- }
432
+ if (resolvedCandidate === undefined) {
433
+ return Result.err(
434
+ PolicyEvaluationErrors.policyDefinitionNotFound(
435
+ policyKey,
436
+ asOf,
437
+ input.contextVersion,
438
+ `Resolved definition "${bestDefinition.id}" is not present among compatible catalog variants`,
439
+ ),
440
+ );
441
+ }
442
+
443
+ return Result.ok(resolvedCandidate);
399
444
  }
400
445
 
401
- const bestResult = this.resolver.resolveBest(
402
- compatibleCandidates.map((candidate) => candidate.definition),
403
- );
404
- if (bestResult.isErr()) {
405
- return Result.err(
406
- PolicyEvaluationErrors.policyDefinitionNotFound(
407
- policyKey,
408
- asOf,
409
- input.contextVersion,
410
- bestResult.errorOrNull()!,
411
- ),
412
- );
413
- }
446
+ #executeEngine(
447
+ policyKey: string,
448
+ definition: PolicyDefinition,
449
+ context: PolicyContext,
450
+ ): Result<PolicyDecision, PolicyEvaluationError> {
451
+ if (definition.isGate()) {
452
+ const gateResult = this.gateEngines.evaluate(
453
+ definition.gateEngineVersion,
454
+ definition.payloadJson,
455
+ context,
456
+ );
457
+ if (gateResult.isErr()) {
458
+ return Result.err(
459
+ PolicyEvaluationErrors.engineFailure(
460
+ policyKey,
461
+ "GATE",
462
+ definition.gateEngineVersion,
463
+ gateResult.errorOrNull()!,
464
+ ),
465
+ );
466
+ }
414
467
 
415
- const bestDefinition = bestResult.getOrNull()!;
416
- const resolvedCandidate = compatibleCandidatesByDefinitionId.get(
417
- bestDefinition.id,
418
- );
419
- if (resolvedCandidate === undefined) {
420
- return Result.err(
421
- PolicyEvaluationErrors.policyDefinitionNotFound(
422
- policyKey,
423
- asOf,
424
- input.contextVersion,
425
- `Resolved definition "${bestDefinition.id}" is not present among compatible catalog variants`,
426
- ),
427
- );
428
- }
468
+ return Result.ok(gateResult.getOrNull()!);
469
+ }
470
+
471
+ if (!definition.isCompute()) {
472
+ return Result.err(
473
+ PolicyEvaluationErrors.policyVariantNotFound({
474
+ policyKey,
475
+ policyKind: definition.kind,
476
+ payloadSchemaVersion: definition.payloadSchemaVersion,
477
+ cause: `Unsupported policy kind "${definition.kind}"`,
478
+ }),
479
+ );
480
+ }
429
481
 
430
- return Result.ok(resolvedCandidate);
431
- }
432
-
433
- #executeEngine(
434
- policyKey: string,
435
- definition: PolicyDefinition,
436
- context: PolicyContext,
437
- ): Result<PolicyDecision, PolicyEvaluationError> {
438
- if (definition.isGate()) {
439
- const gateResult = this.gateEngines.evaluate(
440
- definition.gateEngineVersion,
441
- definition.payloadJson,
442
- context,
443
- );
444
- if (gateResult.isErr()) {
445
- return Result.err(
446
- PolicyEvaluationErrors.engineFailure(
482
+ const computeResult = this.computeRegistry.evaluate(
447
483
  policyKey,
448
- "GATE",
449
- definition.gateEngineVersion,
450
- gateResult.errorOrNull()!,
451
- ),
484
+ definition.computeEngineVersion,
485
+ definition.payloadSchemaVersion,
486
+ definition.payloadJson,
487
+ context,
452
488
  );
453
- }
454
489
 
455
- return Result.ok(gateResult.getOrNull()!);
490
+ if (computeResult.isErr()) {
491
+ return Result.err(
492
+ PolicyEvaluationErrors.engineFailure(
493
+ policyKey,
494
+ "COMPUTE",
495
+ definition.computeEngineVersion,
496
+ computeResult.errorOrNull()!,
497
+ ),
498
+ );
499
+ }
500
+
501
+ return Result.ok(computeResult.getOrNull()!);
456
502
  }
457
-
458
- if (!definition.isCompute()) {
459
- return Result.err(
460
- PolicyEvaluationErrors.policyVariantNotFound({
461
- policyKey,
462
- policyKind: definition.kind,
463
- payloadSchemaVersion: definition.payloadSchemaVersion,
464
- cause: `Unsupported policy kind "${definition.kind}"`,
465
- }),
466
- );
467
- }
468
-
469
- const computeResult = this.computeRegistry.evaluate(
470
- policyKey,
471
- definition.computeEngineVersion,
472
- definition.payloadSchemaVersion,
473
- definition.payloadJson,
474
- context,
475
- );
476
-
477
- if (computeResult.isErr()) {
478
- return Result.err(
479
- PolicyEvaluationErrors.engineFailure(
480
- policyKey,
481
- "COMPUTE",
482
- definition.computeEngineVersion,
483
- computeResult.errorOrNull()!,
484
- ),
485
- );
486
- }
487
-
488
- return Result.ok(computeResult.getOrNull()!);
489
- }
490
503
  }