@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,74 @@
1
+ // Shared types for application errors.
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Severity levels
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ type ErrorSeverity = "low" | "medium" | "high" | "critical";
8
+
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // JSON-safe value types (for metadata serialization)
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ type JsonSafePrimitive = string | number | boolean | null;
14
+
15
+ type JsonSafeValue =
16
+ | JsonSafePrimitive
17
+ | JsonSafeValue[]
18
+ | { [key: string]: JsonSafeValue };
19
+
20
+ type JsonSafeRecord = Record<string, JsonSafeValue>;
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // AppError options (reusable across all error subclasses)
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Options accepted by the AppError constructor.
28
+ * Subclasses can extend or pick from this type.
29
+ */
30
+ type AppErrorOptions = {
31
+ /** Original error or exception that caused this error. */
32
+ cause?: unknown;
33
+
34
+ /** Arbitrary key-value metadata (will be sanitized to JSON-safe values). */
35
+ metadata?: Record<string, unknown>;
36
+
37
+ /** Error category/type for grouping (e.g., "authentication", "validation"). */
38
+ type?: string;
39
+
40
+ /** severity level for alerting/logging prioritization. */
41
+ severity?: ErrorSeverity;
42
+
43
+ /**
44
+ * Identifies a single execution "story" in the system. All operations
45
+ * related to the same logical flow must share the same correlationId.
46
+ */
47
+ correlationId?: string;
48
+
49
+ /**
50
+ * Identifies a specific technical request attempt (each retry may produce
51
+ * a new requestId), even when the correlationId stays the same.
52
+ */
53
+ requestId?: string;
54
+
55
+ /**
56
+ * Identifies a unique business intent (idempotency key); stays the same
57
+ * across technical retries and multiple requests that represent the same intent.
58
+ */
59
+ commandId?: string;
60
+
61
+ /** ISO timestamp of when the error was created (defaults to now). */
62
+ createdAtIso?: string;
63
+
64
+ /** Safe message that can be exposed to end users (no internal details). */
65
+ publicMessage?: string;
66
+ };
67
+
68
+ export type {
69
+ AppErrorOptions,
70
+ ErrorSeverity,
71
+ JsonSafePrimitive,
72
+ JsonSafeRecord,
73
+ JsonSafeValue,
74
+ };
@@ -0,0 +1,22 @@
1
+ import { AppError } from "./app-error";
2
+ import { ErrorCodes } from "./error-codes";
3
+ import type { AppErrorOptions } from "./types";
4
+
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // UnexpectedError
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ class UnexpectedError extends AppError {
10
+ constructor(
11
+ message = "Unexpected error",
12
+ cause?: unknown,
13
+ options?: Omit<AppErrorOptions, "cause">,
14
+ ) {
15
+ super(message, ErrorCodes.unexpected, {
16
+ cause,
17
+ ...options,
18
+ });
19
+ }
20
+ }
21
+
22
+ export { UnexpectedError };
@@ -0,0 +1,4 @@
1
+ export {
2
+ assertJsonSafeMetadata,
3
+ NON_SERIALIZABLE_PLACEHOLDER,
4
+ } from "./json-safe";
@@ -0,0 +1,93 @@
1
+ // Utilities for ensuring metadata is JSON-serializable.
2
+
3
+ import type { JsonSafeRecord, JsonSafeValue } from "../types";
4
+
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Constants
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ const NON_SERIALIZABLE_PLACEHOLDER = "[NonSerializable]";
10
+
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ // Internal helpers
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+
15
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
16
+ if (value === null || typeof value !== "object") return false;
17
+ const proto = Object.getPrototypeOf(value);
18
+ return proto === Object.prototype || proto === null;
19
+ }
20
+
21
+ function sanitizeValue(value: unknown): JsonSafeValue {
22
+ // Null passthrough
23
+ if (value === null) return null;
24
+
25
+ const type = typeof value;
26
+
27
+ // Primitives
28
+ if (type === "string" || type === "number" || type === "boolean") {
29
+ return value as JsonSafeValue;
30
+ }
31
+
32
+ // Non-serializable primitives
33
+ if (
34
+ type === "bigint" ||
35
+ type === "function" ||
36
+ type === "symbol" ||
37
+ value === undefined
38
+ ) {
39
+ return NON_SERIALIZABLE_PLACEHOLDER;
40
+ }
41
+
42
+ // Arrays (recursive)
43
+ if (Array.isArray(value)) {
44
+ return value.map(sanitizeValue);
45
+ }
46
+
47
+ // Date → placeholder (could also use .toISOString() if preferred)
48
+ if (value instanceof Date) {
49
+ return NON_SERIALIZABLE_PLACEHOLDER;
50
+ }
51
+
52
+ // Class instances and non-plain objects → placeholder
53
+ if (!isPlainObject(value)) {
54
+ return NON_SERIALIZABLE_PLACEHOLDER;
55
+ }
56
+
57
+ // Plain object (recursive)
58
+ const result: Record<string, JsonSafeValue> = {};
59
+ for (const [key, val] of Object.entries(value)) {
60
+ result[key] = sanitizeValue(val);
61
+ }
62
+ return result;
63
+ }
64
+
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // Public API
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ /**
70
+ * Ensures metadata is JSON-serializable.
71
+ *
72
+ * **Allowed:** string, number, boolean, null, arrays, and plain objects.
73
+ *
74
+ * **Converted to placeholder:** Date, BigInt, class instances, functions,
75
+ * symbols, undefined.
76
+ *
77
+ * @throws {TypeError} If `input` is not a plain object at the root level.
78
+ */
79
+ function assertJsonSafeMetadata(input: unknown): JsonSafeRecord {
80
+ if (input === undefined) {
81
+ return {};
82
+ }
83
+
84
+ if (!isPlainObject(input)) {
85
+ throw new TypeError(
86
+ "metadata must be a plain object (Record<string, unknown>).",
87
+ );
88
+ }
89
+
90
+ return sanitizeValue(input) as JsonSafeRecord;
91
+ }
92
+
93
+ export { assertJsonSafeMetadata, NON_SERIALIZABLE_PLACEHOLDER };
@@ -0,0 +1,34 @@
1
+ import { ValidationCode } from "../exceptions/validation-code";
2
+ import { ValidationField } from "../exceptions/validation-field";
3
+
4
+ import { AppError } from "./app-error";
5
+ import { ErrorCodes } from "./error-codes";
6
+ import type { AppErrorOptions } from "./types";
7
+
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // ValidationError
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+
12
+ class ValidationError extends AppError {
13
+ constructor(
14
+ field: ValidationField,
15
+ code: ValidationCode,
16
+ message: string,
17
+ options?: AppErrorOptions,
18
+ ) {
19
+ const baseMetadata = {
20
+ field: field.value,
21
+ validationCode: code.value,
22
+ };
23
+ const mergedMetadata = options?.metadata
24
+ ? { ...baseMetadata, ...options.metadata }
25
+ : baseMetadata;
26
+
27
+ super(message, ErrorCodes.validation, {
28
+ ...options,
29
+ metadata: mergedMetadata,
30
+ });
31
+ }
32
+ }
33
+
34
+ export { ValidationError };
@@ -0,0 +1,12 @@
1
+ import { DomainException } from "./domain-exception";
2
+
3
+ class BusinessRuleViolationException extends DomainException {
4
+ constructor(
5
+ public readonly rule: string,
6
+ message: string,
7
+ ) {
8
+ super(message);
9
+ }
10
+ }
11
+
12
+ export { BusinessRuleViolationException };
@@ -0,0 +1,9 @@
1
+ abstract class DomainException extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ Object.setPrototypeOf(this, new.target.prototype);
5
+ this.name = new.target.name;
6
+ }
7
+ }
8
+
9
+ export { DomainException };
@@ -0,0 +1,12 @@
1
+ import { DomainException } from "./domain-exception";
2
+
3
+ class EntityNotFoundException extends DomainException {
4
+ constructor(
5
+ public readonly entityName: string,
6
+ public readonly identifier: string,
7
+ ) {
8
+ super(`${entityName} with identifier ${identifier} was not found.`);
9
+ }
10
+ }
11
+
12
+ export { EntityNotFoundException };
@@ -0,0 +1,15 @@
1
+ import { DomainException } from "./domain-exception";
2
+
3
+ class InvalidStateTransitionException<
4
+ TState extends string = string,
5
+ > extends DomainException {
6
+ constructor(
7
+ public readonly from: TState,
8
+ public readonly to: TState,
9
+ message: string,
10
+ ) {
11
+ super(message);
12
+ }
13
+ }
14
+
15
+ export { InvalidStateTransitionException };
@@ -0,0 +1,9 @@
1
+ import { DomainException } from "./domain-exception";
2
+
3
+ class InvariantViolationException extends DomainException {
4
+ constructor(message: string) {
5
+ super(message);
6
+ }
7
+ }
8
+
9
+ export { InvariantViolationException };
@@ -0,0 +1,39 @@
1
+ // Object-oriented representation of validation codes (similar to Kotlin inline class).
2
+ // Provides common reusable codes while allowing custom codes via `of`.
3
+
4
+ class ValidationCode {
5
+ public readonly value: string;
6
+
7
+ private constructor(value: string) {
8
+ this.value = value;
9
+ }
10
+
11
+ // Factory for custom codes
12
+ public static of(value: string): ValidationCode {
13
+ return new ValidationCode(value);
14
+ }
15
+
16
+ // Common, domain-friendly codes
17
+ public static readonly BLANK = new ValidationCode("blank");
18
+ public static readonly REQUIRED = new ValidationCode("required");
19
+ public static readonly INVALID_FORMAT = new ValidationCode("invalid_format");
20
+ public static readonly INVALID_CHARACTERS = new ValidationCode(
21
+ "invalid_characters",
22
+ );
23
+ public static readonly INVALID_LENGTH = new ValidationCode("invalid_length");
24
+ public static readonly TOO_SHORT = new ValidationCode("too_short");
25
+ public static readonly TOO_LONG = new ValidationCode("too_long");
26
+ public static readonly OUT_OF_RANGE = new ValidationCode("out_of_range");
27
+ public static readonly INVALID_CHECKSUM = new ValidationCode(
28
+ "invalid_checksum",
29
+ );
30
+ public static readonly INVALID_CHECKING_DIGIT = new ValidationCode(
31
+ "invalid_checking_digit",
32
+ );
33
+ public static readonly ALREADY_EXISTS = new ValidationCode("already_exists");
34
+ public static readonly NOT_FOUND = new ValidationCode("not_found");
35
+ public static readonly NOT_ALLOWED = new ValidationCode("not_allowed");
36
+ public static readonly UNEXPECTED = new ValidationCode("unexpected");
37
+ }
38
+
39
+ export { ValidationCode };
@@ -0,0 +1,39 @@
1
+ import { DomainException } from "./domain-exception";
2
+ import { ValidationCode } from "./validation-code";
3
+ import { ValidationField } from "./validation-field";
4
+
5
+ export interface ValidationViolation {
6
+ readonly field: ValidationField;
7
+ readonly code: ValidationCode;
8
+ readonly message: string;
9
+ }
10
+
11
+ class ValidationException extends DomainException {
12
+ constructor(
13
+ public readonly field: ValidationField,
14
+ public readonly code: ValidationCode,
15
+ message: string,
16
+ ) {
17
+ super(message);
18
+ }
19
+ }
20
+
21
+ // Carries multiple validation violations from a single operation (e.g. bulk validation).
22
+ // Use when you need to report all field errors at once rather than failing on the first.
23
+ class MultipleValidationException extends DomainException {
24
+ constructor(public readonly violations: readonly ValidationViolation[]) {
25
+ super(violations.map((v) => v.message).join("; "));
26
+ }
27
+ }
28
+
29
+ class InvalidValueException extends ValidationException {
30
+ constructor(field: ValidationField, code: ValidationCode, message: string) {
31
+ super(field, code, message);
32
+ }
33
+ }
34
+
35
+ export {
36
+ InvalidValueException,
37
+ MultipleValidationException,
38
+ ValidationException,
39
+ };
@@ -0,0 +1,32 @@
1
+ // Object-oriented representation of validation fields.
2
+ // Indicates WHERE a validation error occurred.
3
+ // This is a minimal base class — field catalogs are defined per domain/module.
4
+
5
+ class ValidationField {
6
+ public readonly value: string;
7
+
8
+ private constructor(value: string) {
9
+ this.value = value;
10
+ }
11
+
12
+ // Factory for custom or domain-specific fields
13
+ public static of(value: string): ValidationField {
14
+ return new ValidationField(value);
15
+ }
16
+
17
+ // Creates a nested field path: parent.nested('child') -> 'parent.child'
18
+ public nested(child: string | ValidationField): ValidationField {
19
+ const childValue = child instanceof ValidationField ? child.value : child;
20
+ return new ValidationField(`${this.value}.${childValue}`);
21
+ }
22
+
23
+ public toString(): string {
24
+ return this.value;
25
+ }
26
+
27
+ public equals(other: ValidationField): boolean {
28
+ return this.value === other.value;
29
+ }
30
+ }
31
+
32
+ export { ValidationField };
@@ -0,0 +1,15 @@
1
+ export * from "./errors";
2
+ export * from "./exceptions/validation-field";
3
+ export type {
4
+ LogPayload,
5
+ LoggerPort,
6
+ MetricLabels,
7
+ MetricsPort,
8
+ TraceAttributeValue,
9
+ TraceSpan,
10
+ TracerPort,
11
+ } from "./application";
12
+ export { mapPolicyEvaluationError } from "./application/policy-error-mapper";
13
+ export { Entity, type EntityState } from "./domain/entity";
14
+ export { ValueObject, type DeepReadonly } from "./domain/value-object";
15
+ export * from "./policies";
@@ -0,0 +1,68 @@
1
+ import type { AsOfSource } from "../catalog";
2
+ import type { ContextSeed } from "../context";
3
+ import { Result } from "../../result/result";
4
+ import { isValidDate } from "../../shared/temporal-guards";
5
+
6
+ const DEFAULT_MAX_AS_OF_FUTURE_YEARS = 10;
7
+
8
+ export interface DeriveAsOfOptions {
9
+ readonly maxFutureYears?: number;
10
+ }
11
+
12
+ export class PolicyAsOfResolver {
13
+ private static resolveMaxFutureYears(
14
+ options: DeriveAsOfOptions,
15
+ ): Result<number, string> {
16
+ const maxFutureYears =
17
+ options.maxFutureYears ?? DEFAULT_MAX_AS_OF_FUTURE_YEARS;
18
+
19
+ if (!Number.isInteger(maxFutureYears) || maxFutureYears < 0) {
20
+ return Result.err(
21
+ `deriveAsOf maxFutureYears must be a non-negative integer. Received: ${maxFutureYears}`,
22
+ );
23
+ }
24
+
25
+ return Result.ok(maxFutureYears);
26
+ }
27
+
28
+ static derive(
29
+ source: AsOfSource,
30
+ seed: ContextSeed,
31
+ now: Date,
32
+ options: DeriveAsOfOptions = {},
33
+ ): Result<Date, string> {
34
+ const maxFutureYearsResult =
35
+ PolicyAsOfResolver.resolveMaxFutureYears(options);
36
+ if (maxFutureYearsResult.isErr()) {
37
+ return Result.err(maxFutureYearsResult.errorOrNull()!);
38
+ }
39
+ const maxFutureYears = maxFutureYearsResult.getOrNull()!;
40
+
41
+ switch (source) {
42
+ case "NOW":
43
+ return Result.ok(now);
44
+
45
+ case "CALLER_PROVIDED": {
46
+ const asOf = seed.fields["asOf"];
47
+ if (!isValidDate(asOf)) {
48
+ return Result.err(
49
+ 'asOfSource "CALLER_PROVIDED" requires seed.fields.asOf to be a Date',
50
+ );
51
+ }
52
+
53
+ const maxFuture = new Date(now);
54
+ maxFuture.setFullYear(maxFuture.getFullYear() + maxFutureYears);
55
+ if (asOf > maxFuture) {
56
+ return Result.err(
57
+ `asOf date is more than ${maxFutureYears} years in the future (received ${asOf.toISOString()})`,
58
+ );
59
+ }
60
+
61
+ return Result.ok(asOf);
62
+ }
63
+
64
+ default:
65
+ return Result.err(`Unknown asOfSource: "${source}"`);
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,2 @@
1
+ export { PolicyAsOfResolver } from "./asof";
2
+ export type { DeriveAsOfOptions } from "./asof";
@@ -0,0 +1,12 @@
1
+ // ─── Enums / Literal unions ─────────────────────────────────────────────────
2
+
3
+ export type PolicyKind = "GATE" | "COMPUTE";
4
+
5
+ export type PolicyOwner = "PLATFORM_OWNER" | "TENANT_ADMIN" | "SCHOOL_ADMIN";
6
+
7
+ export type PolicyScopeLevel = "GLOBAL" | "TENANT" | "SCHOOL";
8
+
9
+ /**
10
+ * Defines where the "as of" date comes from for a given policy.
11
+ */
12
+ export type AsOfSource = "NOW" | "CALLER_PROVIDED";
@@ -0,0 +1,21 @@
1
+ import type { PolicyPackage } from "../package/policy-package";
2
+
3
+ import { PolicyCatalog } from "./policy-catalog";
4
+ import { PolicyCatalogEntry } from "./policy-catalog-entry";
5
+
6
+ // ─── Factory ────────────────────────────────────────────────────────────────
7
+
8
+ /**
9
+ * Aggregates catalog entries contributed by each module package into a single
10
+ * PolicyCatalog. The core knows nothing about concrete modules — callers
11
+ * are responsible for passing all relevant packages at composition time.
12
+ */
13
+ export class PolicyCatalogFactory {
14
+ static build(packages: readonly PolicyPackage[]): PolicyCatalog {
15
+ const entries = packages.flatMap((policyPackage) =>
16
+ Array.from(policyPackage.catalogEntries).map(PolicyCatalogEntry.from),
17
+ );
18
+
19
+ return new PolicyCatalog(entries);
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ export type {
2
+ AsOfSource,
3
+ PolicyKind,
4
+ PolicyOwner,
5
+ PolicyScopeLevel,
6
+ } from "./catalog-types";
7
+ export type { PolicyCatalogEntryProps } from "./policy-catalog-entry";
8
+ export { PolicyCatalogEntry } from "./policy-catalog-entry";
9
+ export { PolicyCatalog } from "./policy-catalog";
10
+ export { PolicyCatalogFactory } from "./catalog.instance";
11
+ export { PolicyKey } from "./policy-key";