@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.
Files changed (182) hide show
  1. package/KIT_CONTEXT.md +39 -0
  2. package/README.md +110 -0
  3. package/dist/errors/index.d.ts +2 -0
  4. package/dist/errors/index.js +3 -0
  5. package/dist/exceptions/validation-field.d.ts +2 -0
  6. package/dist/exceptions/validation-field.js +2 -0
  7. package/dist/gate-engine-registry.js +260 -0
  8. package/dist/gate-engine-registry.js.map +1 -0
  9. package/dist/gate-types.d.ts +307 -0
  10. package/dist/gate-v1-payload.schema.js +677 -0
  11. package/dist/gate-v1-payload.schema.js.map +1 -0
  12. package/dist/index.d.ts +88 -0
  13. package/dist/index.js +153 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/outcome.js +92 -0
  16. package/dist/outcome.js.map +1 -0
  17. package/dist/parse-gate-payload.d.ts +136 -0
  18. package/dist/policies/engines/index.d.ts +3 -0
  19. package/dist/policies/engines/index.js +2 -0
  20. package/dist/policies/engines/v1/gate/index.d.ts +115 -0
  21. package/dist/policies/engines/v1/gate/index.js +84 -0
  22. package/dist/policies/engines/v1/gate/index.js.map +1 -0
  23. package/dist/policies/index.d.ts +4 -0
  24. package/dist/policies/index.js +5 -0
  25. package/dist/policy-service.d.ts +541 -0
  26. package/dist/policy-service.js +992 -0
  27. package/dist/policy-service.js.map +1 -0
  28. package/dist/validation-code.js +210 -0
  29. package/dist/validation-code.js.map +1 -0
  30. package/dist/validation-error.d.ts +686 -0
  31. package/dist/validation-error.js +757 -0
  32. package/dist/validation-error.js.map +1 -0
  33. package/dist/validation-field.d.ts +12 -0
  34. package/dist/validation-field.js +23 -0
  35. package/dist/validation-field.js.map +1 -0
  36. package/meta.json +66 -0
  37. package/package.json +59 -0
  38. package/src/core/application/commands/command.ts +28 -0
  39. package/src/core/application/commands/index.ts +2 -0
  40. package/src/core/application/commands/requested-by.ts +97 -0
  41. package/src/core/application/index.ts +31 -0
  42. package/src/core/application/policy-error-mapper.ts +60 -0
  43. package/src/core/application/ports/index.ts +14 -0
  44. package/src/core/application/ports/logger.port.ts +10 -0
  45. package/src/core/application/ports/metrics.port.ts +9 -0
  46. package/src/core/application/ports/policy-port.ts +20 -0
  47. package/src/core/application/ports/repository.port.ts +7 -0
  48. package/src/core/application/ports/temporal-repository.port.ts +14 -0
  49. package/src/core/application/ports/tracer.port.ts +16 -0
  50. package/src/core/application/queries/index.ts +2 -0
  51. package/src/core/application/queries/query.ts +53 -0
  52. package/src/core/application/temporal/index.ts +11 -0
  53. package/src/core/application/temporal/temporal-context.ts +41 -0
  54. package/src/core/application/temporal/temporal-use-case.ts +41 -0
  55. package/src/core/application/use-case.ts +24 -0
  56. package/src/core/config/core-config.instance.ts +14 -0
  57. package/src/core/config/core-config.ts +87 -0
  58. package/src/core/config/index.ts +12 -0
  59. package/src/core/config/policy-reporter.ts +60 -0
  60. package/src/core/config/silent-policy-reporter.ts +5 -0
  61. package/src/core/domain/entity.ts +69 -0
  62. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +16 -0
  63. package/src/core/domain/rulesets/ruleset-registry.ts +71 -0
  64. package/src/core/domain/rulesets/ruleset.contracts.ts +15 -0
  65. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +13 -0
  66. package/src/core/domain/temporal/index.ts +18 -0
  67. package/src/core/domain/temporal/temporal-range.ts +53 -0
  68. package/src/core/domain/temporal/temporal-snapshot.ts +47 -0
  69. package/src/core/domain/temporal/transaction-time.ts +60 -0
  70. package/src/core/domain/temporal/valid-time.ts +57 -0
  71. package/src/core/domain/value-object.ts +30 -0
  72. package/src/core/errors/app-error.ts +85 -0
  73. package/src/core/errors/authentication-error.ts +171 -0
  74. package/src/core/errors/authorization-error.ts +204 -0
  75. package/src/core/errors/business-rule-violation-error.ts +28 -0
  76. package/src/core/errors/conflict-error.ts +246 -0
  77. package/src/core/errors/error-codes.ts +60 -0
  78. package/src/core/errors/idempotency-error.ts +372 -0
  79. package/src/core/errors/index.ts +99 -0
  80. package/src/core/errors/integration-error.ts +157 -0
  81. package/src/core/errors/legacy-incompatible-error.ts +26 -0
  82. package/src/core/errors/not-found-error.ts +27 -0
  83. package/src/core/errors/serialization-error.ts +233 -0
  84. package/src/core/errors/temporal-error.ts +145 -0
  85. package/src/core/errors/types.ts +74 -0
  86. package/src/core/errors/unexpected-error.ts +22 -0
  87. package/src/core/errors/utils/index.ts +4 -0
  88. package/src/core/errors/utils/json-safe.ts +93 -0
  89. package/src/core/errors/validation-error.ts +34 -0
  90. package/src/core/exceptions/business-rule-violation-exception.ts +12 -0
  91. package/src/core/exceptions/domain-exception.ts +9 -0
  92. package/src/core/exceptions/entity-not-found-exception.ts +12 -0
  93. package/src/core/exceptions/invalid-state-transition-exception.ts +15 -0
  94. package/src/core/exceptions/invariant-violation-exception.ts +9 -0
  95. package/src/core/exceptions/validation-code.ts +39 -0
  96. package/src/core/exceptions/validation-exception.ts +39 -0
  97. package/src/core/exceptions/validation-field.ts +32 -0
  98. package/src/core/index.ts +15 -0
  99. package/src/core/policies/asof/asof.ts +68 -0
  100. package/src/core/policies/asof/index.ts +2 -0
  101. package/src/core/policies/catalog/catalog-types.ts +12 -0
  102. package/src/core/policies/catalog/catalog.instance.ts +21 -0
  103. package/src/core/policies/catalog/index.ts +11 -0
  104. package/src/core/policies/catalog/policy-catalog-entry.ts +220 -0
  105. package/src/core/policies/catalog/policy-catalog.ts +124 -0
  106. package/src/core/policies/catalog/policy-key.ts +78 -0
  107. package/src/core/policies/context/context-builder.ts +391 -0
  108. package/src/core/policies/context/context-registry.instance.ts +26 -0
  109. package/src/core/policies/context/context-registry.ts +95 -0
  110. package/src/core/policies/context/context-resolver.ts +31 -0
  111. package/src/core/policies/context/context-seed.ts +32 -0
  112. package/src/core/policies/context/index.ts +21 -0
  113. package/src/core/policies/context/path.ts +152 -0
  114. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +73 -0
  115. package/src/core/policies/defs/index.ts +20 -0
  116. package/src/core/policies/defs/policy-definition-repository.ts +8 -0
  117. package/src/core/policies/defs/policy-definition.ts +192 -0
  118. package/src/core/policies/defs/policy-payload.contracts.ts +13 -0
  119. package/src/core/policies/defs/policy-scope.ts +53 -0
  120. package/src/core/policies/defs/policy-semver.ts +83 -0
  121. package/src/core/policies/engines/compute-engine-registry.ts +57 -0
  122. package/src/core/policies/engines/compute-evaluator-registry.ts +28 -0
  123. package/src/core/policies/engines/compute-payload.ts +5 -0
  124. package/src/core/policies/engines/compute-registry.ts +67 -0
  125. package/src/core/policies/engines/compute-types.ts +51 -0
  126. package/src/core/policies/engines/condition-evaluator-reporter.ts +34 -0
  127. package/src/core/policies/engines/gate-engine-registry.ts +38 -0
  128. package/src/core/policies/engines/gate-payload.ts +5 -0
  129. package/src/core/policies/engines/gate-types.ts +63 -0
  130. package/src/core/policies/engines/index.ts +48 -0
  131. package/src/core/policies/engines/parse-compute-payload.ts +71 -0
  132. package/src/core/policies/engines/parse-gate-payload.ts +68 -0
  133. package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +55 -0
  134. package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +75 -0
  135. package/src/core/policies/engines/v1/compute/index.ts +12 -0
  136. package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +54 -0
  137. package/src/core/policies/engines/v1/condition-evaluator.ts +592 -0
  138. package/src/core/policies/engines/v1/condition-schema.ts +50 -0
  139. package/src/core/policies/engines/v1/condition-types.ts +41 -0
  140. package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +157 -0
  141. package/src/core/policies/engines/v1/gate/gate-types-v1.ts +12 -0
  142. package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +46 -0
  143. package/src/core/policies/engines/v1/gate/index.ts +9 -0
  144. package/src/core/policies/engines/v1/index.ts +13 -0
  145. package/src/core/policies/index.ts +132 -0
  146. package/src/core/policies/package/policy-package.ts +17 -0
  147. package/src/core/policies/policy-ids.ts +60 -0
  148. package/src/core/policies/resolver/index.ts +1 -0
  149. package/src/core/policies/resolver/policy-resolver.ts +57 -0
  150. package/src/core/policies/service/index.ts +10 -0
  151. package/src/core/policies/service/policy-evaluation-error.ts +148 -0
  152. package/src/core/policies/service/policy-service.ts +493 -0
  153. package/src/core/policies/utils/date.ts +7 -0
  154. package/src/core/policies/utils/hash.ts +90 -0
  155. package/src/core/policies/utils/index.ts +3 -0
  156. package/src/core/policies/utils/result.ts +4 -0
  157. package/src/core/result/outcome.ts +158 -0
  158. package/src/core/result/result.ts +153 -0
  159. package/src/core/shared/aggregate-version.ts +11 -0
  160. package/src/core/shared/hashing.ts +38 -0
  161. package/src/core/shared/immutable.ts +46 -0
  162. package/src/core/shared/temporal-guards.ts +22 -0
  163. package/src/core/versioning/domain-event-contracts.ts +73 -0
  164. package/src/core/versioning/version.ts +34 -0
  165. package/src/errors/index.ts +1 -0
  166. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +31 -0
  167. package/src/examples/rulesets/entity/order-invariants-v1.ts +48 -0
  168. package/src/examples/rulesets/entity/order-invariants-v2.ts +28 -0
  169. package/src/examples/rulesets/entity/order.ts +162 -0
  170. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +52 -0
  171. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +27 -0
  172. package/src/examples/rulesets/value-object/cpf.ts +37 -0
  173. package/src/examples/rulesets/value-object/customer.ts +67 -0
  174. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +37 -0
  175. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +27 -0
  176. package/src/examples/rulesets/value-object/person-name.ts +39 -0
  177. package/src/exceptions/validation-field.ts +1 -0
  178. package/src/index.ts +11 -0
  179. package/src/policies/engines/index.ts +1 -0
  180. package/src/policies/engines/v1/gate/index.ts +1 -0
  181. package/src/policies/index.ts +1 -0
  182. package/src/version.ts +7 -0
@@ -0,0 +1,157 @@
1
+ import { type CoreConfig, coreConfig } from "../../../../config";
2
+ import { Outcome } from "../../../../result/outcome";
3
+ import { Result } from "../../../../result/result";
4
+
5
+ import {
6
+ ConditionEvaluatorV1,
7
+ type TracedConditionEvaluation,
8
+ } from "../condition-evaluator";
9
+ import type { ConditionLeafNode, ConditionNode } from "../condition-types";
10
+ import type { GatePayload } from "../../gate-payload";
11
+ import type {
12
+ GateOutcome,
13
+ GateStatus,
14
+ GateTraceLeafSnapshot,
15
+ GateTraceNodeSnapshot,
16
+ PolicyContext,
17
+ PolicyViolation,
18
+ VersionedGateEngine,
19
+ } from "../../gate-types";
20
+
21
+ // ─── Gate engine v1 ─────────────────────────────────────────────────────────
22
+
23
+ export class GateEngineV1 implements VersionedGateEngine {
24
+ public readonly version = 1;
25
+ private readonly coreConfig: CoreConfig;
26
+
27
+ constructor(params: { readonly coreConfig?: CoreConfig } = {}) {
28
+ this.coreConfig = params.coreConfig ?? coreConfig;
29
+ }
30
+
31
+ private allow(): Result<GateOutcome, string> {
32
+ return Result.ok(Outcome.of("ALLOW", { violations: [] }));
33
+ }
34
+
35
+ private deny(violation: PolicyViolation): Result<GateOutcome, string> {
36
+ return Result.ok(
37
+ Outcome.of("DENY", {
38
+ violations: [violation],
39
+ }),
40
+ );
41
+ }
42
+
43
+ private outcomeFromStatus(
44
+ status: GateStatus,
45
+ violation: PolicyViolation,
46
+ ): Result<GateOutcome, string> {
47
+ return status === "ALLOW" ? this.allow() : this.deny(violation);
48
+ }
49
+
50
+ private isLeafNode(node: ConditionNode): node is ConditionLeafNode {
51
+ return "field" in node && "op" in node;
52
+ }
53
+
54
+ private toLeafSnapshot(node: ConditionLeafNode): GateTraceLeafSnapshot {
55
+ return {
56
+ kind: "LEAF",
57
+ field: node.field,
58
+ op: node.op,
59
+ ...(node.allowNull === true ? { allowNull: true } : {}),
60
+ };
61
+ }
62
+
63
+ private toNodeSnapshot(node: ConditionNode): GateTraceNodeSnapshot {
64
+ if (this.isLeafNode(node)) {
65
+ return this.toLeafSnapshot(node);
66
+ }
67
+
68
+ if ("and" in node) {
69
+ return {
70
+ kind: "AND",
71
+ childCount: node.and.length,
72
+ };
73
+ }
74
+
75
+ if ("or" in node) {
76
+ return {
77
+ kind: "OR",
78
+ childCount: node.or.length,
79
+ };
80
+ }
81
+
82
+ return {
83
+ kind: "NOT",
84
+ };
85
+ }
86
+
87
+ private buildDenyViolation(
88
+ conditionResult: TracedConditionEvaluation,
89
+ ): PolicyViolation {
90
+ return {
91
+ code: "DENY_CONDITION_MET",
92
+ message: `Gate denied at ${conditionResult.trace.conditionPath}`,
93
+ trace: {
94
+ conditionPath: conditionResult.trace.conditionPath,
95
+ decisiveNode: this.toNodeSnapshot(conditionResult.trace.decisiveNode),
96
+ lastEvaluatedLeaf: this.toLeafSnapshot(
97
+ conditionResult.trace.lastEvaluatedLeaf,
98
+ ),
99
+ lastEvaluatedLeafPath: conditionResult.trace.lastEvaluatedLeafPath,
100
+ lastEvaluatedLeafResult: conditionResult.trace.lastEvaluatedLeafResult,
101
+ field: conditionResult.trace.lastEvaluatedLeaf.field,
102
+ op: conditionResult.trace.lastEvaluatedLeaf.op,
103
+ },
104
+ };
105
+ }
106
+
107
+ private normalizePayload(payload: GatePayload): {
108
+ readonly allowIf: ConditionNode;
109
+ readonly defaultOutcome: "ALLOW" | "DENY";
110
+ } {
111
+ if ("condition" in payload) {
112
+ return {
113
+ allowIf: payload.condition,
114
+ defaultOutcome: "DENY",
115
+ };
116
+ }
117
+
118
+ return payload;
119
+ }
120
+
121
+ /**
122
+ * Evaluates a GATE payload against a context object.
123
+ * Pure function — no side effects.
124
+ *
125
+ * Receives an already-parsed `GatePayload`; the registry is responsible
126
+ * for parsing from raw `unknown` before calling this method.
127
+ * Returns Result for technical failures (condition evaluation errors).
128
+ * The Outcome inside carries the business decision (ALLOW/DENY).
129
+ */
130
+ evaluate(
131
+ payload: GatePayload,
132
+ context: PolicyContext,
133
+ ): Result<GateOutcome, string> {
134
+ const gatePayload = this.normalizePayload(payload);
135
+ const conditionEvaluator = new ConditionEvaluatorV1(
136
+ context,
137
+ this.coreConfig.getConditionEvaluationOptions(this.version),
138
+ );
139
+
140
+ const isAllowedResult = conditionEvaluator.evaluateWithTrace(
141
+ gatePayload.allowIf,
142
+ );
143
+ if (isAllowedResult.isErr()) {
144
+ return Result.err(isAllowedResult.errorOrNull()!);
145
+ }
146
+
147
+ const isAllowed = isAllowedResult.getOrNull()!;
148
+ if (isAllowed.matched) {
149
+ return this.allow();
150
+ }
151
+
152
+ return this.outcomeFromStatus(
153
+ gatePayload.defaultOutcome,
154
+ this.buildDenyViolation(isAllowed),
155
+ );
156
+ }
157
+ }
@@ -0,0 +1,12 @@
1
+ // ─── Gate v1 payload types ───────────────────────────────────────────────────
2
+ // The condition-tree DSL types are shared across the v1 engine family and live
3
+ // in engines/v1. Imported here for the GatePayloadV1 definition only — not
4
+ // re-exported; consumers should import condition types from engines/v1.
5
+ import type { ConditionNode } from "../condition-types";
6
+
7
+ export type GatePayloadV1 =
8
+ | { readonly condition: ConditionNode }
9
+ | {
10
+ readonly allowIf: ConditionNode;
11
+ readonly defaultOutcome: "ALLOW" | "DENY";
12
+ };
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+
3
+ import { Result } from "../../../../result/result";
4
+
5
+ import type { GatePayloadV1 } from "./gate-types-v1";
6
+ import {
7
+ conditionLeafNodeSchema,
8
+ conditionNodeSchema,
9
+ } from "../condition-schema";
10
+
11
+ // Re-export so gate/v1 public API remains unchanged.
12
+ export { conditionLeafNodeSchema, conditionNodeSchema };
13
+
14
+ export class GatePayloadSchemaV1 {
15
+ static readonly schema = z.union([
16
+ z
17
+ .object({
18
+ condition: conditionNodeSchema,
19
+ })
20
+ .strict(),
21
+ z
22
+ .object({
23
+ allowIf: conditionNodeSchema,
24
+ defaultOutcome: z.enum(["ALLOW", "DENY"]),
25
+ })
26
+ .strict(),
27
+ ]);
28
+
29
+ static parse(payload: unknown): Result<GatePayloadV1, string> {
30
+ const parsed = GatePayloadSchemaV1.schema.safeParse(payload);
31
+ if (!parsed.success) {
32
+ const details = parsed.error.issues
33
+ .map((issue) => {
34
+ const path = issue.path.join(".");
35
+ return path.length > 0 ? `${path}: ${issue.message}` : issue.message;
36
+ })
37
+ .join("; ");
38
+
39
+ return Result.err(`Invalid gate payload: ${details}`);
40
+ }
41
+
42
+ return Result.ok(parsed.data as GatePayloadV1);
43
+ }
44
+ }
45
+
46
+ export const gatePayloadSchema = GatePayloadSchemaV1.schema;
@@ -0,0 +1,9 @@
1
+ export { ConditionEvaluatorV1 } from "../condition-evaluator";
2
+ export { GateEngineV1 } from "./gate-engine-v1";
3
+ export type { GatePayloadV1 } from "./gate-types-v1";
4
+ export {
5
+ conditionLeafNodeSchema,
6
+ conditionNodeSchema,
7
+ gatePayloadSchema,
8
+ GatePayloadSchemaV1,
9
+ } from "./gate-v1-payload.schema";
@@ -0,0 +1,13 @@
1
+ export { ConditionEvaluatorV1 } from "./condition-evaluator";
2
+ export {
3
+ conditionLeafNodeSchema,
4
+ conditionNodeSchema,
5
+ } from "./condition-schema";
6
+ export type {
7
+ ConditionAndNode,
8
+ ConditionLeafNode,
9
+ ConditionNode,
10
+ ConditionNotNode,
11
+ ConditionOp,
12
+ ConditionOrNode,
13
+ } from "./condition-types";
@@ -0,0 +1,132 @@
1
+ // Policy Engine — Pure Core
2
+ // Re-exports all public types, classes and utilities.
3
+
4
+ // ─── Core Config ────────────────────────────────────────────────────────────
5
+ export type { CoreConfigOptions, CoreObservabilityConfig } from "../config";
6
+ export { CoreConfig, coreConfig } from "../config";
7
+
8
+ // ─── Result (technical execution) ───────────────────────────────────────────
9
+ export { Err, Ok, Result } from "../result/result";
10
+
11
+ // ─── Outcome (business decision) ───────────────────────────────────────────
12
+ export type { CommonOutcomeStatus } from "../result/outcome";
13
+ export { Outcome } from "../result/outcome";
14
+
15
+ // ─── Utils ──────────────────────────────────────────────────────────────────
16
+ export { PolicyHashing } from "./utils";
17
+ export type {
18
+ PolicyDecisionId,
19
+ PolicyDefinitionId,
20
+ SchoolId,
21
+ TenantId,
22
+ } from "./policy-ids";
23
+ export {
24
+ asPolicyDecisionId,
25
+ asPolicyDefinitionId,
26
+ asSchoolId,
27
+ asTenantId,
28
+ } from "./policy-ids";
29
+
30
+ // ─── Catalog ────────────────────────────────────────────────────────────────
31
+ export type {
32
+ AsOfSource,
33
+ PolicyCatalogEntryProps,
34
+ PolicyKind,
35
+ PolicyOwner,
36
+ PolicyScopeLevel,
37
+ } from "./catalog";
38
+ export { PolicyCatalog, PolicyCatalogFactory, PolicyKey } from "./catalog";
39
+
40
+ // ─── Definitions ────────────────────────────────────────────────────────────
41
+ export type {
42
+ BasePolicyDefinitionProps,
43
+ ComputePolicyDefinitionProps,
44
+ FindCandidatesParams,
45
+ GatePolicyDefinitionProps,
46
+ PolicyDefinitionProps,
47
+ PolicyDefinitionStatus,
48
+ PolicyScope,
49
+ ScopeChain,
50
+ } from "./defs";
51
+ export {
52
+ InMemoryPolicyDefinitionRepository,
53
+ PolicyDefinition,
54
+ PolicyScopeMatcher,
55
+ } from "./defs";
56
+
57
+ // ─── Context ────────────────────────────────────────────────────────────────
58
+ export type {
59
+ ContextResolverCircuitBreakerOptions,
60
+ ContextResolverResilienceOptions,
61
+ ContextResolverRetryOptions,
62
+ ContextSeed,
63
+ ContextValueResolver,
64
+ PolicyContextBuilderOptions,
65
+ } from "./context";
66
+ export {
67
+ ContextSeedValidator,
68
+ ContextResolverRegistry,
69
+ contextResolverRegistry,
70
+ PolicyContextBuilder,
71
+ PolicyContextPath,
72
+ registerNamespacedContextResolvers,
73
+ registerNamespacedContextResolversIn,
74
+ } from "./context";
75
+
76
+ // ─── AsOf ───────────────────────────────────────────────────────────────────
77
+ export type { DeriveAsOfOptions } from "./asof";
78
+ export { PolicyAsOfResolver } from "./asof";
79
+
80
+ // ─── Engines ────────────────────────────────────────────────────────────────
81
+ export type {
82
+ ConditionEvaluationCause,
83
+ ConditionEvaluationOptions,
84
+ ConditionEvaluationReport,
85
+ ConditionEvaluationReportLevel,
86
+ ConditionEvaluationReportTag,
87
+ ConditionEvaluatorReporter,
88
+ GateOutcome,
89
+ GateOutcomeData,
90
+ GatePayload,
91
+ GateStatus,
92
+ GateTraceLeafSnapshot,
93
+ GateTraceNodeSnapshot,
94
+ GateViolationTrace,
95
+ PolicyContext,
96
+ PolicyViolation,
97
+ VersionedGateEngine,
98
+ } from "./engines";
99
+ export type {
100
+ ComputeEvaluator,
101
+ ComputeEvaluatorRegistration,
102
+ ComputeOutcome,
103
+ ComputeOutcomeData,
104
+ ComputeStatus,
105
+ VersionedComputeEngine,
106
+ } from "./engines";
107
+ export {
108
+ ComputeEngineRegistry,
109
+ ComputeEvaluatorRegistry,
110
+ ComputePayloadParsers,
111
+ ComputePayloadParserRegistry,
112
+ ComputeRegistry,
113
+ GateEngineRegistry,
114
+ GatePayloadParsers,
115
+ } from "./engines";
116
+
117
+ // ─── Resolver ───────────────────────────────────────────────────────────────
118
+ export { PolicyResolver } from "./resolver";
119
+
120
+ // ─── Service ────────────────────────────────────────────────────────────────
121
+ export type {
122
+ EvaluateInput,
123
+ PolicyDecision,
124
+ PolicyEvaluationError,
125
+ PolicyEvaluationResult,
126
+ PolicyServiceParams,
127
+ PolicyServiceOptions,
128
+ } from "./service";
129
+ export { PolicyEvaluationErrors, PolicyService } from "./service";
130
+
131
+ // ─── Package (contribution protocol) ────────────────────────────────────────
132
+ export type { PolicyPackage } from "./package/policy-package";
@@ -0,0 +1,17 @@
1
+ import type { PolicyCatalogEntryProps } from "../catalog";
2
+ import type { PolicyDefinition } from "../defs";
3
+ import type { ComputeEvaluatorRegistration } from "../engines";
4
+
5
+ /**
6
+ * Protocol of contribution: a module declares what it wants to add to the
7
+ * policy system without the core knowing which module it is.
8
+ *
9
+ * - catalogEntries: static policy descriptors the catalog will register.
10
+ * - definitions: default policy definitions (rules/parameters) to seed.
11
+ * - evaluators: compute evaluator registrations keyed by policy/version.
12
+ */
13
+ export interface PolicyPackage {
14
+ readonly catalogEntries: readonly PolicyCatalogEntryProps[];
15
+ readonly definitions: readonly PolicyDefinition[];
16
+ readonly evaluators: readonly ComputeEvaluatorRegistration[];
17
+ }
@@ -0,0 +1,60 @@
1
+ import { ValidationCode } from "../exceptions/validation-code";
2
+ import { InvalidValueException } from "../exceptions/validation-exception";
3
+ import { ValidationField } from "../exceptions/validation-field";
4
+
5
+ type Brand<TValue, TBrand extends string> = TValue & {
6
+ readonly __brand: TBrand;
7
+ };
8
+
9
+ export type PolicyDefinitionId = Brand<string, "PolicyDefinitionId">;
10
+ export type PolicyDecisionId = Brand<string, "PolicyDecisionId">;
11
+ export type TenantId = Brand<string, "TenantId">;
12
+ export type SchoolId = Brand<string, "SchoolId">;
13
+
14
+ const POLICY_DEFINITION_ID_FIELD = ValidationField.of("policyDefinitionId");
15
+ const POLICY_DECISION_ID_FIELD = ValidationField.of("policyDecisionId");
16
+ const TENANT_ID_FIELD = ValidationField.of("tenantId");
17
+ const SCHOOL_ID_FIELD = ValidationField.of("schoolId");
18
+
19
+ function assertIdString(value: unknown, field: ValidationField): string {
20
+ if (typeof value !== "string") {
21
+ throw new InvalidValueException(
22
+ field,
23
+ ValidationCode.INVALID_FORMAT,
24
+ `${field.value} must be a string, got ${typeof value}`,
25
+ );
26
+ }
27
+
28
+ if (value.trim().length === 0) {
29
+ throw new InvalidValueException(
30
+ field,
31
+ ValidationCode.BLANK,
32
+ `${field.value} must be a non-empty string`,
33
+ );
34
+ }
35
+
36
+ return value;
37
+ }
38
+
39
+ function brandId<TBrand extends string>(
40
+ value: unknown,
41
+ field: ValidationField,
42
+ ): Brand<string, TBrand> {
43
+ return assertIdString(value, field) as Brand<string, TBrand>;
44
+ }
45
+
46
+ export function asPolicyDefinitionId(value: string): PolicyDefinitionId {
47
+ return brandId<"PolicyDefinitionId">(value, POLICY_DEFINITION_ID_FIELD);
48
+ }
49
+
50
+ export function asPolicyDecisionId(value: string): PolicyDecisionId {
51
+ return brandId<"PolicyDecisionId">(value, POLICY_DECISION_ID_FIELD);
52
+ }
53
+
54
+ export function asTenantId(value: string): TenantId {
55
+ return brandId<"TenantId">(value, TENANT_ID_FIELD);
56
+ }
57
+
58
+ export function asSchoolId(value: string): SchoolId {
59
+ return brandId<"SchoolId">(value, SCHOOL_ID_FIELD);
60
+ }
@@ -0,0 +1 @@
1
+ export { PolicyResolver } from "./policy-resolver";
@@ -0,0 +1,57 @@
1
+ import type { PolicyDefinition } from "../defs";
2
+ import { PolicyScopeMatcher } from "../defs";
3
+ import { comparePolicySemver } from "../defs/policy-semver";
4
+ import { Result } from "../../result/result";
5
+
6
+ /**
7
+ * Selects the best policy definition from a list of already-filtered candidates.
8
+ *
9
+ * Ordering criteria (highest priority first):
10
+ * 1. Scope specificity: SCHOOL (3) > TENANT (2) > GLOBAL (1)
11
+ * 2. Priority: higher number wins
12
+ * 3. publishedAt descending (fallback to createdAt)
13
+ * 4. Engine version descending (prefer-latest-engine-version — deterministic tiebreaker)
14
+ * 5. policyVersion descending (semver — final deterministic tiebreaker)
15
+ */
16
+ export class PolicyResolver {
17
+ resolveBest(
18
+ candidates: readonly PolicyDefinition[],
19
+ ): Result<PolicyDefinition, string> {
20
+ if (candidates.length === 0) {
21
+ return Result.err(
22
+ "No matching policy definition found (candidates list is empty)",
23
+ );
24
+ }
25
+
26
+ const sorted = [...candidates].sort((a, b) => {
27
+ // 1) Scope specificity (higher weight = more specific)
28
+ const scopeDiff =
29
+ PolicyScopeMatcher.weight(b.scope.level) -
30
+ PolicyScopeMatcher.weight(a.scope.level);
31
+ if (scopeDiff !== 0) return scopeDiff;
32
+
33
+ // 2) Priority DESC
34
+ const priorityDiff = b.priority - a.priority;
35
+ if (priorityDiff !== 0) return priorityDiff;
36
+
37
+ // 3) publishedAt DESC (fallback createdAt)
38
+ const aDate = a.publishedAt ?? a.createdAt;
39
+ const bDate = b.publishedAt ?? b.createdAt;
40
+ const dateDiff = bDate.getTime() - aDate.getTime();
41
+ if (dateDiff !== 0) return dateDiff;
42
+
43
+ // 4) Engine version DESC — prefers the definition running on the most
44
+ // capable engine when scope/priority/date are identical.
45
+ const aEngine = a.gateEngineVersion ?? a.computeEngineVersion ?? 0;
46
+ const bEngine = b.gateEngineVersion ?? b.computeEngineVersion ?? 0;
47
+ const engineDiff = bEngine - aEngine;
48
+ if (engineDiff !== 0) return engineDiff;
49
+
50
+ // 5) policyVersion DESC (semver) — final deterministic tiebreaker so
51
+ // the winner never depends on repository return order.
52
+ return comparePolicySemver(b.policyVersion, a.policyVersion);
53
+ });
54
+
55
+ return Result.ok(sorted[0]);
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ export { PolicyService } from "./policy-service";
2
+ export { PolicyEvaluationErrors } from "./policy-evaluation-error";
3
+ export type {
4
+ EvaluateInput,
5
+ PolicyDecision,
6
+ PolicyEvaluationError,
7
+ PolicyEvaluationResult,
8
+ PolicyServiceOptions,
9
+ PolicyServiceParams,
10
+ } from "./policy-service";
@@ -0,0 +1,148 @@
1
+ import type { PolicyKind } from "../catalog";
2
+
3
+ // ─── Error Union ─────────────────────────────────────────────────────────────
4
+
5
+ export type PolicyEvaluationError =
6
+ | {
7
+ readonly kind: "INVALID_CONTEXT";
8
+ readonly stage: "SEED_VALIDATION" | "AS_OF_DERIVATION" | "CONTEXT_BUILD";
9
+ readonly policyKey: string;
10
+ readonly message: string;
11
+ readonly cause: string;
12
+ }
13
+ | {
14
+ readonly kind: "INVALID_POLICY_KEY";
15
+ readonly rawPolicyKey: string;
16
+ readonly message: string;
17
+ readonly cause: string;
18
+ }
19
+ | {
20
+ readonly kind: "POLICY_NOT_FOUND";
21
+ readonly policyKey: string;
22
+ readonly message: string;
23
+ readonly cause: string;
24
+ }
25
+ | {
26
+ readonly kind: "POLICY_DEFINITION_NOT_FOUND";
27
+ readonly policyKey: string;
28
+ readonly contextVersion: number;
29
+ readonly asOf: Date;
30
+ readonly message: string;
31
+ readonly cause: string;
32
+ }
33
+ | {
34
+ readonly kind: "POLICY_VARIANT_NOT_FOUND";
35
+ readonly policyKey: string;
36
+ readonly policyKind: PolicyKind;
37
+ readonly payloadSchemaVersion: number;
38
+ readonly gateEngineVersion?: number;
39
+ readonly computeEngineVersion?: number;
40
+ readonly message: string;
41
+ readonly cause: string;
42
+ }
43
+ | {
44
+ readonly kind: "ENGINE_FAILURE";
45
+ readonly policyKey: string;
46
+ readonly engine: PolicyKind;
47
+ readonly engineVersion: number;
48
+ readonly message: string;
49
+ readonly cause: string;
50
+ };
51
+
52
+ // ─── Factory object ─────────────────────────────────────────────────────────
53
+
54
+ export class PolicyEvaluationErrors {
55
+ static invalidContext(
56
+ stage: Extract<PolicyEvaluationError, { kind: "INVALID_CONTEXT" }>["stage"],
57
+ policyKey: string,
58
+ cause: string,
59
+ ): PolicyEvaluationError {
60
+ return {
61
+ kind: "INVALID_CONTEXT",
62
+ stage,
63
+ policyKey,
64
+ message: "Policy evaluation context is invalid",
65
+ cause,
66
+ };
67
+ }
68
+
69
+ static invalidPolicyKey(
70
+ rawPolicyKey: string,
71
+ cause: string,
72
+ ): PolicyEvaluationError {
73
+ return {
74
+ kind: "INVALID_POLICY_KEY",
75
+ rawPolicyKey,
76
+ message: "Policy key is invalid",
77
+ cause,
78
+ };
79
+ }
80
+
81
+ static policyNotFound(
82
+ policyKey: string,
83
+ cause: string,
84
+ ): PolicyEvaluationError {
85
+ return {
86
+ kind: "POLICY_NOT_FOUND",
87
+ policyKey,
88
+ message: "Policy is not registered in the catalog",
89
+ cause,
90
+ };
91
+ }
92
+
93
+ static policyDefinitionNotFound(
94
+ policyKey: string,
95
+ asOf: Date,
96
+ contextVersion: number,
97
+ cause: string,
98
+ ): PolicyEvaluationError {
99
+ return {
100
+ kind: "POLICY_DEFINITION_NOT_FOUND",
101
+ policyKey,
102
+ contextVersion,
103
+ asOf,
104
+ message: "No published policy definition matched the evaluation inputs",
105
+ cause,
106
+ };
107
+ }
108
+
109
+ static policyVariantNotFound(params: {
110
+ readonly policyKey: string;
111
+ readonly policyKind: PolicyKind;
112
+ readonly payloadSchemaVersion: number;
113
+ readonly gateEngineVersion?: number;
114
+ readonly computeEngineVersion?: number;
115
+ readonly cause: string;
116
+ }): PolicyEvaluationError {
117
+ return {
118
+ kind: "POLICY_VARIANT_NOT_FOUND",
119
+ policyKey: params.policyKey,
120
+ policyKind: params.policyKind,
121
+ payloadSchemaVersion: params.payloadSchemaVersion,
122
+ ...(params.gateEngineVersion !== undefined
123
+ ? { gateEngineVersion: params.gateEngineVersion }
124
+ : {}),
125
+ ...(params.computeEngineVersion !== undefined
126
+ ? { computeEngineVersion: params.computeEngineVersion }
127
+ : {}),
128
+ message: "No catalog variant matched the selected policy definition",
129
+ cause: params.cause,
130
+ };
131
+ }
132
+
133
+ static engineFailure(
134
+ policyKey: string,
135
+ engine: PolicyKind,
136
+ engineVersion: number,
137
+ cause: string,
138
+ ): PolicyEvaluationError {
139
+ return {
140
+ kind: "ENGINE_FAILURE",
141
+ policyKey,
142
+ engine,
143
+ engineVersion,
144
+ message: "Policy engine evaluation failed",
145
+ cause,
146
+ };
147
+ }
148
+ }