@cullet/erp-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/KIT_CONTEXT.md +39 -0
- package/README.md +110 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/exceptions/validation-field.d.ts +2 -0
- package/dist/exceptions/validation-field.js +2 -0
- package/dist/gate-engine-registry.js +260 -0
- package/dist/gate-engine-registry.js.map +1 -0
- package/dist/gate-types.d.ts +307 -0
- package/dist/gate-v1-payload.schema.js +677 -0
- package/dist/gate-v1-payload.schema.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/outcome.js +92 -0
- package/dist/outcome.js.map +1 -0
- package/dist/parse-gate-payload.d.ts +136 -0
- package/dist/policies/engines/index.d.ts +3 -0
- package/dist/policies/engines/index.js +2 -0
- package/dist/policies/engines/v1/gate/index.d.ts +115 -0
- package/dist/policies/engines/v1/gate/index.js +84 -0
- package/dist/policies/engines/v1/gate/index.js.map +1 -0
- package/dist/policies/index.d.ts +4 -0
- package/dist/policies/index.js +5 -0
- package/dist/policy-service.d.ts +541 -0
- package/dist/policy-service.js +992 -0
- package/dist/policy-service.js.map +1 -0
- package/dist/validation-code.js +210 -0
- package/dist/validation-code.js.map +1 -0
- package/dist/validation-error.d.ts +686 -0
- package/dist/validation-error.js +757 -0
- package/dist/validation-error.js.map +1 -0
- package/dist/validation-field.d.ts +12 -0
- package/dist/validation-field.js +23 -0
- package/dist/validation-field.js.map +1 -0
- package/meta.json +66 -0
- package/package.json +59 -0
- package/src/core/application/commands/command.ts +28 -0
- package/src/core/application/commands/index.ts +2 -0
- package/src/core/application/commands/requested-by.ts +97 -0
- package/src/core/application/index.ts +31 -0
- package/src/core/application/policy-error-mapper.ts +60 -0
- package/src/core/application/ports/index.ts +14 -0
- package/src/core/application/ports/logger.port.ts +10 -0
- package/src/core/application/ports/metrics.port.ts +9 -0
- package/src/core/application/ports/policy-port.ts +20 -0
- package/src/core/application/ports/repository.port.ts +7 -0
- package/src/core/application/ports/temporal-repository.port.ts +14 -0
- package/src/core/application/ports/tracer.port.ts +16 -0
- package/src/core/application/queries/index.ts +2 -0
- package/src/core/application/queries/query.ts +53 -0
- package/src/core/application/temporal/index.ts +11 -0
- package/src/core/application/temporal/temporal-context.ts +41 -0
- package/src/core/application/temporal/temporal-use-case.ts +41 -0
- package/src/core/application/use-case.ts +24 -0
- package/src/core/config/core-config.instance.ts +14 -0
- package/src/core/config/core-config.ts +87 -0
- package/src/core/config/index.ts +12 -0
- package/src/core/config/policy-reporter.ts +60 -0
- package/src/core/config/silent-policy-reporter.ts +5 -0
- package/src/core/domain/entity.ts +69 -0
- package/src/core/domain/rulesets/entity-ruleset.contracts.ts +16 -0
- package/src/core/domain/rulesets/ruleset-registry.ts +71 -0
- package/src/core/domain/rulesets/ruleset.contracts.ts +15 -0
- package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +13 -0
- package/src/core/domain/temporal/index.ts +18 -0
- package/src/core/domain/temporal/temporal-range.ts +53 -0
- package/src/core/domain/temporal/temporal-snapshot.ts +47 -0
- package/src/core/domain/temporal/transaction-time.ts +60 -0
- package/src/core/domain/temporal/valid-time.ts +57 -0
- package/src/core/domain/value-object.ts +30 -0
- package/src/core/errors/app-error.ts +85 -0
- package/src/core/errors/authentication-error.ts +171 -0
- package/src/core/errors/authorization-error.ts +204 -0
- package/src/core/errors/business-rule-violation-error.ts +28 -0
- package/src/core/errors/conflict-error.ts +246 -0
- package/src/core/errors/error-codes.ts +60 -0
- package/src/core/errors/idempotency-error.ts +372 -0
- package/src/core/errors/index.ts +99 -0
- package/src/core/errors/integration-error.ts +157 -0
- package/src/core/errors/legacy-incompatible-error.ts +26 -0
- package/src/core/errors/not-found-error.ts +27 -0
- package/src/core/errors/serialization-error.ts +233 -0
- package/src/core/errors/temporal-error.ts +145 -0
- package/src/core/errors/types.ts +74 -0
- package/src/core/errors/unexpected-error.ts +22 -0
- package/src/core/errors/utils/index.ts +4 -0
- package/src/core/errors/utils/json-safe.ts +93 -0
- package/src/core/errors/validation-error.ts +34 -0
- package/src/core/exceptions/business-rule-violation-exception.ts +12 -0
- package/src/core/exceptions/domain-exception.ts +9 -0
- package/src/core/exceptions/entity-not-found-exception.ts +12 -0
- package/src/core/exceptions/invalid-state-transition-exception.ts +15 -0
- package/src/core/exceptions/invariant-violation-exception.ts +9 -0
- package/src/core/exceptions/validation-code.ts +39 -0
- package/src/core/exceptions/validation-exception.ts +39 -0
- package/src/core/exceptions/validation-field.ts +32 -0
- package/src/core/index.ts +15 -0
- package/src/core/policies/asof/asof.ts +68 -0
- package/src/core/policies/asof/index.ts +2 -0
- package/src/core/policies/catalog/catalog-types.ts +12 -0
- package/src/core/policies/catalog/catalog.instance.ts +21 -0
- package/src/core/policies/catalog/index.ts +11 -0
- package/src/core/policies/catalog/policy-catalog-entry.ts +220 -0
- package/src/core/policies/catalog/policy-catalog.ts +124 -0
- package/src/core/policies/catalog/policy-key.ts +78 -0
- package/src/core/policies/context/context-builder.ts +391 -0
- package/src/core/policies/context/context-registry.instance.ts +26 -0
- package/src/core/policies/context/context-registry.ts +95 -0
- package/src/core/policies/context/context-resolver.ts +31 -0
- package/src/core/policies/context/context-seed.ts +32 -0
- package/src/core/policies/context/index.ts +21 -0
- package/src/core/policies/context/path.ts +152 -0
- package/src/core/policies/defs/in-memory-policy-definition-repo.ts +73 -0
- package/src/core/policies/defs/index.ts +20 -0
- package/src/core/policies/defs/policy-definition-repository.ts +8 -0
- package/src/core/policies/defs/policy-definition.ts +192 -0
- package/src/core/policies/defs/policy-payload.contracts.ts +13 -0
- package/src/core/policies/defs/policy-scope.ts +53 -0
- package/src/core/policies/defs/policy-semver.ts +83 -0
- package/src/core/policies/engines/compute-engine-registry.ts +57 -0
- package/src/core/policies/engines/compute-evaluator-registry.ts +28 -0
- package/src/core/policies/engines/compute-payload.ts +5 -0
- package/src/core/policies/engines/compute-registry.ts +67 -0
- package/src/core/policies/engines/compute-types.ts +51 -0
- package/src/core/policies/engines/condition-evaluator-reporter.ts +34 -0
- package/src/core/policies/engines/gate-engine-registry.ts +38 -0
- package/src/core/policies/engines/gate-payload.ts +5 -0
- package/src/core/policies/engines/gate-types.ts +63 -0
- package/src/core/policies/engines/index.ts +48 -0
- package/src/core/policies/engines/parse-compute-payload.ts +71 -0
- package/src/core/policies/engines/parse-gate-payload.ts +68 -0
- package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +55 -0
- package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +75 -0
- package/src/core/policies/engines/v1/compute/index.ts +12 -0
- package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +54 -0
- package/src/core/policies/engines/v1/condition-evaluator.ts +592 -0
- package/src/core/policies/engines/v1/condition-schema.ts +50 -0
- package/src/core/policies/engines/v1/condition-types.ts +41 -0
- package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +157 -0
- package/src/core/policies/engines/v1/gate/gate-types-v1.ts +12 -0
- package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +46 -0
- package/src/core/policies/engines/v1/gate/index.ts +9 -0
- package/src/core/policies/engines/v1/index.ts +13 -0
- package/src/core/policies/index.ts +132 -0
- package/src/core/policies/package/policy-package.ts +17 -0
- package/src/core/policies/policy-ids.ts +60 -0
- package/src/core/policies/resolver/index.ts +1 -0
- package/src/core/policies/resolver/policy-resolver.ts +57 -0
- package/src/core/policies/service/index.ts +10 -0
- package/src/core/policies/service/policy-evaluation-error.ts +148 -0
- package/src/core/policies/service/policy-service.ts +493 -0
- package/src/core/policies/utils/date.ts +7 -0
- package/src/core/policies/utils/hash.ts +90 -0
- package/src/core/policies/utils/index.ts +3 -0
- package/src/core/policies/utils/result.ts +4 -0
- package/src/core/result/outcome.ts +158 -0
- package/src/core/result/result.ts +153 -0
- package/src/core/shared/aggregate-version.ts +11 -0
- package/src/core/shared/hashing.ts +38 -0
- package/src/core/shared/immutable.ts +46 -0
- package/src/core/shared/temporal-guards.ts +22 -0
- package/src/core/versioning/domain-event-contracts.ts +73 -0
- package/src/core/versioning/version.ts +34 -0
- package/src/errors/index.ts +1 -0
- package/src/examples/rulesets/entity/order-creation-rules-v1.ts +31 -0
- package/src/examples/rulesets/entity/order-invariants-v1.ts +48 -0
- package/src/examples/rulesets/entity/order-invariants-v2.ts +28 -0
- package/src/examples/rulesets/entity/order.ts +162 -0
- package/src/examples/rulesets/value-object/cpf-rules-v1.ts +52 -0
- package/src/examples/rulesets/value-object/cpf-rules-v2.ts +27 -0
- package/src/examples/rulesets/value-object/cpf.ts +37 -0
- package/src/examples/rulesets/value-object/customer.ts +67 -0
- package/src/examples/rulesets/value-object/person-name-rules-v1.ts +37 -0
- package/src/examples/rulesets/value-object/person-name-rules-v2.ts +27 -0
- package/src/examples/rulesets/value-object/person-name.ts +39 -0
- package/src/exceptions/validation-field.ts +1 -0
- package/src/index.ts +11 -0
- package/src/policies/engines/index.ts +1 -0
- package/src/policies/engines/v1/gate/index.ts +1 -0
- package/src/policies/index.ts +1 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type CoreConfig, coreConfig } from "../../config";
|
|
2
|
+
import type { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import { ComputeEngineRegistry } from "./compute-engine-registry";
|
|
5
|
+
import { ComputeEvaluatorRegistry } from "./compute-evaluator-registry";
|
|
6
|
+
import type { PolicyContext } from "./gate-types";
|
|
7
|
+
import type {
|
|
8
|
+
ComputeEvaluator,
|
|
9
|
+
ComputeEvaluatorRegistration,
|
|
10
|
+
ComputeOutcome,
|
|
11
|
+
VersionedComputeEngine,
|
|
12
|
+
} from "./compute-types";
|
|
13
|
+
import { ComputeEngineV1 } from "./v1/compute";
|
|
14
|
+
|
|
15
|
+
export interface ComputeRegistryOptions {
|
|
16
|
+
readonly coreConfig?: CoreConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ComputeRegistry {
|
|
20
|
+
private readonly engines = new ComputeEngineRegistry();
|
|
21
|
+
private readonly evaluators = new ComputeEvaluatorRegistry();
|
|
22
|
+
|
|
23
|
+
constructor(params: ComputeRegistryOptions = {}) {
|
|
24
|
+
const resolvedCoreConfig = params.coreConfig ?? coreConfig;
|
|
25
|
+
|
|
26
|
+
this.engines.register(
|
|
27
|
+
new ComputeEngineV1({
|
|
28
|
+
coreConfig: resolvedCoreConfig,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
register(registration: ComputeEvaluatorRegistration): void {
|
|
34
|
+
this.evaluators.register(registration);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
registerEngine(engine: VersionedComputeEngine): void {
|
|
38
|
+
this.engines.register(engine);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getEngine(version: number): VersionedComputeEngine | undefined {
|
|
42
|
+
return this.engines.get(version);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getEvaluator(
|
|
46
|
+
policyKey: string,
|
|
47
|
+
version: number,
|
|
48
|
+
): ComputeEvaluator | undefined {
|
|
49
|
+
return this.evaluators.get(policyKey, version);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
evaluate(
|
|
53
|
+
policyKey: string,
|
|
54
|
+
computeEngineVersion: number,
|
|
55
|
+
payloadSchemaVersion: number,
|
|
56
|
+
payload: unknown,
|
|
57
|
+
context: PolicyContext,
|
|
58
|
+
): Result<ComputeOutcome, string> {
|
|
59
|
+
return this.engines.evaluate(computeEngineVersion, {
|
|
60
|
+
policyKey,
|
|
61
|
+
payloadSchemaVersion,
|
|
62
|
+
payload,
|
|
63
|
+
context,
|
|
64
|
+
evaluators: this.evaluators,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Outcome } from "../../result/outcome";
|
|
2
|
+
import type { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import type { ComputePayload } from "./compute-payload";
|
|
5
|
+
import type { PolicyContext, PolicyViolation } from "./gate-types";
|
|
6
|
+
|
|
7
|
+
// ─── Compute outcome (business decision) ────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export type ComputeStatus = "OK" | "NOT_APPLICABLE" | "ERROR";
|
|
10
|
+
|
|
11
|
+
export interface ComputeOutcomeData {
|
|
12
|
+
readonly values: unknown | null;
|
|
13
|
+
readonly violations: readonly PolicyViolation[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Semantic outcome of a COMPUTE policy evaluation. */
|
|
17
|
+
export type ComputeOutcome = Outcome<ComputeStatus, ComputeOutcomeData>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pure compute operation: receives a resolved value + context, returns a
|
|
21
|
+
* business decision. Registration metadata (policy key/version) lives outside
|
|
22
|
+
* this contract.
|
|
23
|
+
*
|
|
24
|
+
* The engine resolves the structural payload before calling the evaluator:
|
|
25
|
+
* - `params` -> `payload.data`
|
|
26
|
+
* - `decision-table` -> matching `rule.result` or `default`
|
|
27
|
+
*
|
|
28
|
+
* Result wraps technical failures (invalid value shape).
|
|
29
|
+
* Outcome carries the semantic business decision.
|
|
30
|
+
*/
|
|
31
|
+
export interface ComputeEvaluator {
|
|
32
|
+
evaluate(
|
|
33
|
+
resolvedValue: unknown,
|
|
34
|
+
context: Record<string, unknown>,
|
|
35
|
+
): Result<ComputeOutcome, string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ComputeEvaluatorRegistration {
|
|
39
|
+
readonly policyKey: string;
|
|
40
|
+
readonly version: number;
|
|
41
|
+
readonly evaluator: ComputeEvaluator;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface VersionedComputeEngine {
|
|
45
|
+
readonly version: number;
|
|
46
|
+
evaluate(
|
|
47
|
+
payload: ComputePayload,
|
|
48
|
+
context: PolicyContext,
|
|
49
|
+
evaluator: ComputeEvaluator,
|
|
50
|
+
): Result<ComputeOutcome, string>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ConditionEvaluationCause {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly message: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type ConditionEvaluationReportLevel = "warn" | "error";
|
|
7
|
+
|
|
8
|
+
export type ConditionEvaluationReportTag =
|
|
9
|
+
| "MISSING_CONTEXT_FIELD"
|
|
10
|
+
| "NULLISH_NUMERIC_OPERAND_NOT_ALLOWED"
|
|
11
|
+
| "NULLISH_DATE_OPERAND_NOT_ALLOWED"
|
|
12
|
+
| "INVALID_DATE_OPERAND"
|
|
13
|
+
| "CONDITION_EVAL_THREW";
|
|
14
|
+
|
|
15
|
+
export interface ConditionEvaluationReport {
|
|
16
|
+
readonly occurredAt: Date;
|
|
17
|
+
readonly level: ConditionEvaluationReportLevel;
|
|
18
|
+
readonly tag: ConditionEvaluationReportTag;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly engineVersion: number;
|
|
21
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConditionEvaluatorReporter {
|
|
25
|
+
warn(report: ConditionEvaluationReport): void;
|
|
26
|
+
error(report: ConditionEvaluationReport): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Either fully silent (no reporter, no version stamp) or fully observable
|
|
30
|
+
// (reporter paired with engineVersion so every report carries its origin).
|
|
31
|
+
export type ConditionEvaluationOptions = {
|
|
32
|
+
readonly reporter: ConditionEvaluatorReporter;
|
|
33
|
+
readonly engineVersion: number;
|
|
34
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Result } from "../../result/result";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
GateOutcome,
|
|
5
|
+
PolicyContext,
|
|
6
|
+
VersionedGateEngine,
|
|
7
|
+
} from "./gate-types";
|
|
8
|
+
import { GatePayloadParsers } from "./parse-gate-payload";
|
|
9
|
+
|
|
10
|
+
export class GateEngineRegistry {
|
|
11
|
+
private readonly engines = new Map<number, VersionedGateEngine>();
|
|
12
|
+
|
|
13
|
+
register(engine: VersionedGateEngine): void {
|
|
14
|
+
this.engines.set(engine.version, engine);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get(version: number): VersionedGateEngine | undefined {
|
|
18
|
+
return this.engines.get(version);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
evaluate(
|
|
22
|
+
version: number,
|
|
23
|
+
payload: unknown,
|
|
24
|
+
context: PolicyContext,
|
|
25
|
+
): Result<GateOutcome, string> {
|
|
26
|
+
const engine = this.engines.get(version);
|
|
27
|
+
if (!engine) {
|
|
28
|
+
return Result.err(`No gate engine registered for version "${version}"`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parsedPayload = GatePayloadParsers.parse(version, payload);
|
|
32
|
+
if (parsedPayload.isErr()) {
|
|
33
|
+
return Result.err(parsedPayload.errorOrNull()!);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return engine.evaluate(parsedPayload.getOrNull()!, context);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Outcome } from "../../result/outcome";
|
|
2
|
+
import type { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import type { GatePayload } from "./gate-payload";
|
|
5
|
+
|
|
6
|
+
// ─── Shared gate types (version-agnostic) ───────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type PolicyContext = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export interface GateTraceLeafSnapshot {
|
|
11
|
+
readonly kind: "LEAF";
|
|
12
|
+
readonly field: string;
|
|
13
|
+
readonly op: string;
|
|
14
|
+
readonly allowNull?: true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type GateTraceNodeSnapshot =
|
|
18
|
+
| GateTraceLeafSnapshot
|
|
19
|
+
| {
|
|
20
|
+
readonly kind: "AND" | "OR";
|
|
21
|
+
readonly childCount: number;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
readonly kind: "NOT";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface GateViolationTrace {
|
|
28
|
+
readonly conditionPath: string;
|
|
29
|
+
readonly decisiveNode: GateTraceNodeSnapshot;
|
|
30
|
+
readonly lastEvaluatedLeaf: GateTraceLeafSnapshot;
|
|
31
|
+
readonly lastEvaluatedLeafPath: string;
|
|
32
|
+
readonly lastEvaluatedLeafResult: boolean;
|
|
33
|
+
readonly field: string;
|
|
34
|
+
readonly op: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PolicyViolation {
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
readonly trace?: GateViolationTrace;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type GateStatus = "ALLOW" | "DENY";
|
|
44
|
+
|
|
45
|
+
export interface GateOutcomeData {
|
|
46
|
+
readonly violations: readonly PolicyViolation[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Semantic outcome of a GATE policy evaluation. */
|
|
50
|
+
export type GateOutcome = Outcome<GateStatus, GateOutcomeData>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Contract every versioned gate engine must satisfy.
|
|
54
|
+
* Receives an already-parsed `GatePayload`; parsing from raw `unknown` is
|
|
55
|
+
* the registry's responsibility via `parseGatePayload`.
|
|
56
|
+
*/
|
|
57
|
+
export interface VersionedGateEngine {
|
|
58
|
+
readonly version: number;
|
|
59
|
+
evaluate(
|
|
60
|
+
payload: GatePayload,
|
|
61
|
+
context: PolicyContext,
|
|
62
|
+
): Result<GateOutcome, string>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type { ComputePayload, ComputePayloadV1 } from "./compute-payload";
|
|
2
|
+
export type {
|
|
3
|
+
ComputeEvaluator,
|
|
4
|
+
ComputeEvaluatorRegistration,
|
|
5
|
+
ComputeOutcome,
|
|
6
|
+
ComputeOutcomeData,
|
|
7
|
+
ComputeStatus,
|
|
8
|
+
VersionedComputeEngine,
|
|
9
|
+
} from "./compute-types";
|
|
10
|
+
export type {
|
|
11
|
+
ConditionEvaluationCause,
|
|
12
|
+
ConditionEvaluationOptions,
|
|
13
|
+
ConditionEvaluationReport,
|
|
14
|
+
ConditionEvaluationReportLevel,
|
|
15
|
+
ConditionEvaluationReportTag,
|
|
16
|
+
ConditionEvaluatorReporter,
|
|
17
|
+
} from "./condition-evaluator-reporter";
|
|
18
|
+
export { ComputeEngineRegistry } from "./compute-engine-registry";
|
|
19
|
+
export { ComputeEvaluatorRegistry } from "./compute-evaluator-registry";
|
|
20
|
+
export { ComputeRegistry } from "./compute-registry";
|
|
21
|
+
export { GateEngineRegistry } from "./gate-engine-registry";
|
|
22
|
+
export type { GatePayload, GatePayloadV1 } from "./gate-payload";
|
|
23
|
+
export type {
|
|
24
|
+
GateOutcome,
|
|
25
|
+
GateOutcomeData,
|
|
26
|
+
GateStatus,
|
|
27
|
+
GateTraceLeafSnapshot,
|
|
28
|
+
GateTraceNodeSnapshot,
|
|
29
|
+
GateViolationTrace,
|
|
30
|
+
PolicyContext,
|
|
31
|
+
PolicyViolation,
|
|
32
|
+
VersionedGateEngine,
|
|
33
|
+
} from "./gate-types";
|
|
34
|
+
export {
|
|
35
|
+
ComputePayloadParsers,
|
|
36
|
+
ComputePayloadParserRegistry,
|
|
37
|
+
} from "./parse-compute-payload";
|
|
38
|
+
export type { ComputePayloadParser } from "./parse-compute-payload";
|
|
39
|
+
export {
|
|
40
|
+
GatePayloadParsers,
|
|
41
|
+
GatePayloadParserRegistry,
|
|
42
|
+
} from "./parse-gate-payload";
|
|
43
|
+
export type { GatePayloadParser } from "./parse-gate-payload";
|
|
44
|
+
export type {
|
|
45
|
+
ComputeDecisionTablePayload,
|
|
46
|
+
ComputeDecisionTableRule,
|
|
47
|
+
ComputeParamsPayload,
|
|
48
|
+
} from "./v1/compute";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Result as ResultType } from "../../result/result";
|
|
2
|
+
import { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import type { ComputePayload } from "./compute-payload";
|
|
5
|
+
import { ComputePayloadSchemaV1 } from "./v1/compute/compute-payload.schema";
|
|
6
|
+
|
|
7
|
+
export type ComputePayloadParser = (
|
|
8
|
+
payload: unknown,
|
|
9
|
+
) => ResultType<ComputePayload, string>;
|
|
10
|
+
|
|
11
|
+
export class ComputePayloadParserRegistry {
|
|
12
|
+
private readonly parsers = new Map<number, ComputePayloadParser>();
|
|
13
|
+
|
|
14
|
+
register(payloadSchemaVersion: number, parser: ComputePayloadParser): void {
|
|
15
|
+
this.parsers.set(payloadSchemaVersion, parser);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(payloadSchemaVersion: number): ComputePayloadParser | undefined {
|
|
19
|
+
return this.parsers.get(payloadSchemaVersion);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
parse(payloadSchemaVersion: number, payload: unknown) {
|
|
23
|
+
const parser = this.parsers.get(payloadSchemaVersion);
|
|
24
|
+
|
|
25
|
+
if (parser === undefined) {
|
|
26
|
+
return Result.err(
|
|
27
|
+
`No compute payload parser registered for schema version "${payloadSchemaVersion}"`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parser(payload);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class ComputePayloadParsers {
|
|
36
|
+
private static readonly registry = new ComputePayloadParserRegistry();
|
|
37
|
+
private static defaultsRegistered = false;
|
|
38
|
+
|
|
39
|
+
// Registers the built-in v1 parser lazily, the first time the default
|
|
40
|
+
// dispatcher is touched. Done here — rather than as a top-level module
|
|
41
|
+
// statement — so the package's `"sideEffects": false` flag stays honest and
|
|
42
|
+
// a tree-shaking bundler cannot drop the v1 registration and leave the
|
|
43
|
+
// compute engine unable to parse v1 payloads at runtime.
|
|
44
|
+
private static ensureDefaults(): void {
|
|
45
|
+
if (ComputePayloadParsers.defaultsRegistered) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
ComputePayloadParsers.defaultsRegistered = true;
|
|
49
|
+
ComputePayloadParsers.registry.register(1, ComputePayloadSchemaV1.parse);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static register(
|
|
53
|
+
payloadSchemaVersion: number,
|
|
54
|
+
parser: ComputePayloadParser,
|
|
55
|
+
): void {
|
|
56
|
+
ComputePayloadParsers.ensureDefaults();
|
|
57
|
+
ComputePayloadParsers.registry.register(payloadSchemaVersion, parser);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static get(payloadSchemaVersion: number): ComputePayloadParser | undefined {
|
|
61
|
+
ComputePayloadParsers.ensureDefaults();
|
|
62
|
+
return ComputePayloadParsers.registry.get(payloadSchemaVersion);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static parse(payloadSchemaVersion: number, payload: unknown) {
|
|
66
|
+
ComputePayloadParsers.ensureDefaults();
|
|
67
|
+
return ComputePayloadParsers.registry.parse(payloadSchemaVersion, payload);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type { ComputePayload };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Result as ResultType } from "../../result/result";
|
|
2
|
+
import { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import type { GatePayload } from "./gate-payload";
|
|
5
|
+
import { GatePayloadSchemaV1 } from "./v1/gate/gate-v1-payload.schema";
|
|
6
|
+
|
|
7
|
+
export type GatePayloadParser = (
|
|
8
|
+
payload: unknown,
|
|
9
|
+
) => ResultType<GatePayload, string>;
|
|
10
|
+
|
|
11
|
+
export class GatePayloadParserRegistry {
|
|
12
|
+
private readonly parsers = new Map<number, GatePayloadParser>();
|
|
13
|
+
|
|
14
|
+
register(engineVersion: number, parser: GatePayloadParser): void {
|
|
15
|
+
this.parsers.set(engineVersion, parser);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(engineVersion: number): GatePayloadParser | undefined {
|
|
19
|
+
return this.parsers.get(engineVersion);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
parse(engineVersion: number, payload: unknown) {
|
|
23
|
+
const parser = this.parsers.get(engineVersion);
|
|
24
|
+
|
|
25
|
+
if (parser === undefined) {
|
|
26
|
+
return Result.err(
|
|
27
|
+
`No gate payload parser registered for engine version "${engineVersion}"`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parser(payload);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class GatePayloadParsers {
|
|
36
|
+
private static readonly registry = new GatePayloadParserRegistry();
|
|
37
|
+
private static defaultsRegistered = false;
|
|
38
|
+
|
|
39
|
+
// Registers the built-in v1 parser lazily, the first time the default
|
|
40
|
+
// dispatcher is touched. Done here — rather than as a top-level module
|
|
41
|
+
// statement — so the package's `"sideEffects": false` flag stays honest and
|
|
42
|
+
// a tree-shaking bundler cannot drop the v1 registration and leave the gate
|
|
43
|
+
// engine unable to parse v1 payloads at runtime.
|
|
44
|
+
private static ensureDefaults(): void {
|
|
45
|
+
if (GatePayloadParsers.defaultsRegistered) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
GatePayloadParsers.defaultsRegistered = true;
|
|
49
|
+
GatePayloadParsers.registry.register(1, GatePayloadSchemaV1.parse);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static register(engineVersion: number, parser: GatePayloadParser): void {
|
|
53
|
+
GatePayloadParsers.ensureDefaults();
|
|
54
|
+
GatePayloadParsers.registry.register(engineVersion, parser);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static get(engineVersion: number): GatePayloadParser | undefined {
|
|
58
|
+
GatePayloadParsers.ensureDefaults();
|
|
59
|
+
return GatePayloadParsers.registry.get(engineVersion);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static parse(engineVersion: number, payload: unknown) {
|
|
63
|
+
GatePayloadParsers.ensureDefaults();
|
|
64
|
+
return GatePayloadParsers.registry.parse(engineVersion, payload);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type { GatePayload };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type CoreConfig, coreConfig } from "../../../../config";
|
|
2
|
+
import { Result } from "../../../../result/result";
|
|
3
|
+
|
|
4
|
+
import type { ComputePayloadV1 as ComputePayload } from "./compute-payload.schema";
|
|
5
|
+
import { DecisionTableResolverV1 } from "./resolve-decision-table-v1";
|
|
6
|
+
import type {
|
|
7
|
+
ComputeEvaluator,
|
|
8
|
+
ComputeOutcome,
|
|
9
|
+
VersionedComputeEngine,
|
|
10
|
+
} from "../../compute-types";
|
|
11
|
+
import type { PolicyContext } from "../../gate-types";
|
|
12
|
+
|
|
13
|
+
// ─── Compute engine v1 ───────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const COMPUTE_ENGINE_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
export class ComputeEngineV1 implements VersionedComputeEngine {
|
|
18
|
+
readonly version = COMPUTE_ENGINE_VERSION;
|
|
19
|
+
private readonly coreConfig: CoreConfig;
|
|
20
|
+
|
|
21
|
+
constructor(params: { readonly coreConfig?: CoreConfig } = {}) {
|
|
22
|
+
this.coreConfig = params.coreConfig ?? coreConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Evaluates an already-parsed ComputePayload against a context.
|
|
27
|
+
* Pure function — no side effects.
|
|
28
|
+
*
|
|
29
|
+
* For `params`: forwards `payload.data` directly to the evaluator.
|
|
30
|
+
* For `decision-table`: evaluates the rules in order and passes the `result`
|
|
31
|
+
* of the first one that satisfies its conditions, or `default` if none match.
|
|
32
|
+
*
|
|
33
|
+
* The evaluator is responsible for validating and transforming the final value.
|
|
34
|
+
*/
|
|
35
|
+
evaluate(
|
|
36
|
+
payload: ComputePayload,
|
|
37
|
+
context: PolicyContext,
|
|
38
|
+
evaluator: ComputeEvaluator,
|
|
39
|
+
): Result<ComputeOutcome, string> {
|
|
40
|
+
if (payload.type === "params") {
|
|
41
|
+
return evaluator.evaluate(payload.data, context);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolvedResult = DecisionTableResolverV1.resolve(
|
|
45
|
+
payload,
|
|
46
|
+
context,
|
|
47
|
+
this.coreConfig.getConditionEvaluationOptions(COMPUTE_ENGINE_VERSION),
|
|
48
|
+
);
|
|
49
|
+
if (resolvedResult.isErr()) {
|
|
50
|
+
return Result.err(resolvedResult.errorOrNull()!);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return evaluator.evaluate(resolvedResult.getOrNull()!, context);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { Result } from "../../../../result/result";
|
|
4
|
+
|
|
5
|
+
import { conditionNodeSchema } from "../condition-schema";
|
|
6
|
+
import type { ConditionNode } from "../condition-types";
|
|
7
|
+
|
|
8
|
+
// ─── Pattern 1: Params ──────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export interface ComputeParamsPayload {
|
|
11
|
+
readonly type: "params";
|
|
12
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ─── Pattern 2: Decision table ──────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export interface ComputeDecisionTableRule<R = unknown> {
|
|
18
|
+
readonly conditions: ConditionNode;
|
|
19
|
+
readonly result: R;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ComputeDecisionTablePayload<R = unknown> {
|
|
23
|
+
readonly type: "decision-table";
|
|
24
|
+
readonly rules: readonly ComputeDecisionTableRule<R>[];
|
|
25
|
+
readonly default: R;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Union ──────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export type ComputePayloadV1 =
|
|
31
|
+
| ComputeDecisionTablePayload<unknown>
|
|
32
|
+
| ComputeParamsPayload;
|
|
33
|
+
|
|
34
|
+
// ─── Zod schemas ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const computeDecisionTableRuleSchema = z.object({
|
|
37
|
+
conditions: conditionNodeSchema,
|
|
38
|
+
result: z.unknown(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export class ComputePayloadSchemaV1 {
|
|
42
|
+
static readonly schema = z.union([
|
|
43
|
+
z
|
|
44
|
+
.object({
|
|
45
|
+
type: z.literal("decision-table"),
|
|
46
|
+
rules: z.array(computeDecisionTableRuleSchema).min(1),
|
|
47
|
+
default: z.unknown(),
|
|
48
|
+
})
|
|
49
|
+
.strict(),
|
|
50
|
+
z
|
|
51
|
+
.object({
|
|
52
|
+
type: z.literal("params"),
|
|
53
|
+
data: z.record(z.string(), z.unknown()),
|
|
54
|
+
})
|
|
55
|
+
.strict(),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
static parse(payload: unknown): Result<ComputePayloadV1, string> {
|
|
59
|
+
const parsed = ComputePayloadSchemaV1.schema.safeParse(payload);
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
const details = parsed.error.issues
|
|
62
|
+
.map((issue) => {
|
|
63
|
+
const path = issue.path.join(".");
|
|
64
|
+
return path.length > 0 ? `${path}: ${issue.message}` : issue.message;
|
|
65
|
+
})
|
|
66
|
+
.join("; ");
|
|
67
|
+
|
|
68
|
+
return Result.err(`Invalid compute payload: ${details}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Result.ok(parsed.data as ComputePayloadV1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const computePayloadSchema = ComputePayloadSchemaV1.schema;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { ComputeEngineV1 } from "./compute-engine-v1";
|
|
2
|
+
export type {
|
|
3
|
+
ComputeDecisionTablePayload,
|
|
4
|
+
ComputeDecisionTableRule,
|
|
5
|
+
ComputeParamsPayload,
|
|
6
|
+
ComputePayloadV1,
|
|
7
|
+
} from "./compute-payload.schema";
|
|
8
|
+
export {
|
|
9
|
+
computePayloadSchema,
|
|
10
|
+
ComputePayloadSchemaV1,
|
|
11
|
+
} from "./compute-payload.schema";
|
|
12
|
+
export { DecisionTableResolverV1 } from "./resolve-decision-table-v1";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Result } from "../../../../result/result";
|
|
2
|
+
|
|
3
|
+
import type { ConditionEvaluationOptions } from "../../condition-evaluator-reporter";
|
|
4
|
+
|
|
5
|
+
import { ConditionEvaluatorV1 } from "../condition-evaluator";
|
|
6
|
+
import type { ComputeDecisionTablePayload } from "./compute-payload.schema";
|
|
7
|
+
|
|
8
|
+
const COMPUTE_CONDITION_EVAL_FAILED_TAG = "COMPUTE_CONDITION_EVAL_FAILED";
|
|
9
|
+
|
|
10
|
+
export class DecisionTableResolverV1 {
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly payload: ComputeDecisionTablePayload,
|
|
13
|
+
private readonly context: Record<string, unknown>,
|
|
14
|
+
private readonly conditionEvaluationOptions: ConditionEvaluationOptions,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
private wrapConditionEvaluationError(error: string): Result<unknown, string> {
|
|
18
|
+
return Result.err(`${COMPUTE_CONDITION_EVAL_FAILED_TAG}: ${error}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resolve(): Result<unknown, string> {
|
|
22
|
+
const evaluator = new ConditionEvaluatorV1(
|
|
23
|
+
this.context,
|
|
24
|
+
this.conditionEvaluationOptions,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
for (const rule of this.payload.rules) {
|
|
28
|
+
const conditionResult = evaluator.evaluate(rule.conditions);
|
|
29
|
+
if (conditionResult.isErr()) {
|
|
30
|
+
return this.wrapConditionEvaluationError(
|
|
31
|
+
conditionResult.errorOrNull()!,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (conditionResult.getOrNull()) {
|
|
36
|
+
return Result.ok(rule.result);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Result.ok(this.payload.default);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static resolve(
|
|
44
|
+
payload: ComputeDecisionTablePayload,
|
|
45
|
+
context: Record<string, unknown>,
|
|
46
|
+
conditionEvaluationOptions: ConditionEvaluationOptions,
|
|
47
|
+
): Result<unknown, string> {
|
|
48
|
+
return new DecisionTableResolverV1(
|
|
49
|
+
payload,
|
|
50
|
+
context,
|
|
51
|
+
conditionEvaluationOptions,
|
|
52
|
+
).resolve();
|
|
53
|
+
}
|
|
54
|
+
}
|