@cullet/erp-core 1.2.0 → 1.4.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 (218) hide show
  1. package/KIT_CONTEXT.md +5 -3
  2. package/README.md +160 -0
  3. package/dist/abac/index.cjs +7 -0
  4. package/dist/abac/index.d.cts +2 -0
  5. package/dist/abac/index.d.ts +2 -0
  6. package/dist/abac/index.js +2 -0
  7. package/dist/app-error.cjs +193 -0
  8. package/dist/app-error.cjs.map +1 -0
  9. package/dist/app-error.d.cts +108 -0
  10. package/dist/app-error.js +170 -0
  11. package/dist/app-error.js.map +1 -0
  12. package/dist/application/index.cjs +27 -0
  13. package/dist/application/index.d.cts +3 -0
  14. package/dist/application/index.d.ts +2 -1
  15. package/dist/application/index.js +2 -1
  16. package/dist/authorization-error.cjs +157 -0
  17. package/dist/authorization-error.cjs.map +1 -0
  18. package/dist/authorization-error.d.cts +113 -0
  19. package/dist/authorization-error.d.ts +113 -0
  20. package/dist/authorization-error.js +152 -0
  21. package/dist/authorization-error.js.map +1 -0
  22. package/dist/authorizer.port.d.cts +130 -0
  23. package/dist/authorizer.port.d.ts +130 -0
  24. package/dist/composite-authorizer.d.cts +216 -0
  25. package/dist/composite-authorizer.d.ts +216 -0
  26. package/dist/condition-evaluator.cjs +565 -0
  27. package/dist/condition-evaluator.cjs.map +1 -0
  28. package/dist/condition-evaluator.js +536 -0
  29. package/dist/condition-evaluator.js.map +1 -0
  30. package/dist/core-config.d.cts +172 -0
  31. package/dist/{gate-types.d.ts → core-config.d.ts} +78 -77
  32. package/dist/domain/index.cjs +17 -0
  33. package/dist/domain/index.d.cts +4 -0
  34. package/dist/domain/index.d.ts +2 -1
  35. package/dist/domain/index.js +2 -1
  36. package/dist/domain-event-contracts.cjs +34 -0
  37. package/dist/domain-event-contracts.cjs.map +1 -0
  38. package/dist/domain-event-contracts.d.cts +27 -0
  39. package/dist/domain-event-contracts.js +2 -1
  40. package/dist/domain-event-contracts.js.map +1 -1
  41. package/dist/domain-exception.cjs +17 -0
  42. package/dist/domain-exception.cjs.map +1 -0
  43. package/dist/domain-exception.d.cts +7 -0
  44. package/dist/entity.cjs +126 -0
  45. package/dist/entity.cjs.map +1 -0
  46. package/dist/entity.js +115 -0
  47. package/dist/entity.js.map +1 -0
  48. package/dist/errors/index.cjs +43 -0
  49. package/dist/errors/index.d.cts +4 -0
  50. package/dist/errors/index.d.ts +2 -1
  51. package/dist/errors/index.js +4 -2
  52. package/dist/exceptions/index.cjs +17 -0
  53. package/dist/exceptions/index.d.cts +5 -0
  54. package/dist/exceptions/validation-field.cjs +3 -0
  55. package/dist/exceptions/validation-field.d.cts +2 -0
  56. package/dist/gate-engine-registry.cjs +309 -0
  57. package/dist/gate-engine-registry.cjs.map +1 -0
  58. package/dist/gate-engine-registry.d.cts +82 -0
  59. package/dist/gate-engine-registry.d.ts +3 -2
  60. package/dist/gate-engine-registry.js +2 -1
  61. package/dist/gate-engine-registry.js.map +1 -1
  62. package/dist/gate-v1-payload.schema.cjs +76 -0
  63. package/dist/gate-v1-payload.schema.cjs.map +1 -0
  64. package/dist/gate-v1-payload.schema.js +1 -533
  65. package/dist/gate-v1-payload.schema.js.map +1 -1
  66. package/dist/hashing.cjs +66 -0
  67. package/dist/hashing.cjs.map +1 -0
  68. package/dist/immutable.cjs +77 -0
  69. package/dist/immutable.cjs.map +1 -0
  70. package/dist/immutable.d.cts +6 -0
  71. package/dist/index.cjs +181 -0
  72. package/dist/index.cjs.map +1 -0
  73. package/dist/index.d.cts +37 -0
  74. package/dist/index.d.ts +19 -12
  75. package/dist/index.js +15 -9
  76. package/dist/index.js.map +1 -1
  77. package/dist/invalid-state-transition-exception.cjs +47 -0
  78. package/dist/invalid-state-transition-exception.cjs.map +1 -0
  79. package/dist/invariant-violation-exception.cjs +16 -0
  80. package/dist/invariant-violation-exception.cjs.map +1 -0
  81. package/dist/not-found-error.cjs +65 -0
  82. package/dist/not-found-error.cjs.map +1 -0
  83. package/dist/not-found-error.js +1 -1
  84. package/dist/outcome.cjs +97 -0
  85. package/dist/outcome.cjs.map +1 -0
  86. package/dist/outcome.d.cts +58 -0
  87. package/dist/outcome.d.ts +1 -83
  88. package/dist/parse-gate-payload.d.cts +62 -0
  89. package/dist/parse-gate-payload.d.ts +2 -2
  90. package/dist/path.d.cts +90 -0
  91. package/dist/path.d.ts +2 -2
  92. package/dist/plugin.cjs +79 -0
  93. package/dist/plugin.cjs.map +1 -0
  94. package/dist/plugin.d.cts +85 -0
  95. package/dist/plugins/index.cjs +3 -0
  96. package/dist/plugins/index.d.cts +2 -0
  97. package/dist/policies/engines/index.cjs +10 -0
  98. package/dist/policies/engines/index.d.cts +4 -0
  99. package/dist/policies/engines/index.d.ts +1 -1
  100. package/dist/policies/engines/v1/gate/index.cjs +92 -0
  101. package/dist/policies/engines/v1/gate/index.cjs.map +1 -0
  102. package/dist/policies/engines/v1/gate/index.d.cts +121 -0
  103. package/dist/policies/engines/v1/gate/index.d.ts +2 -2
  104. package/dist/policies/engines/v1/gate/index.js +2 -1
  105. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  106. package/dist/policies/index.cjs +41 -0
  107. package/dist/policies/index.d.cts +8 -0
  108. package/dist/policies/index.d.ts +3 -2
  109. package/dist/policies/index.js +2 -2
  110. package/dist/policy-bridge.cjs +437 -0
  111. package/dist/policy-bridge.cjs.map +1 -0
  112. package/dist/policy-bridge.d.cts +185 -0
  113. package/dist/policy-bridge.d.ts +185 -0
  114. package/dist/policy-bridge.js +396 -0
  115. package/dist/policy-bridge.js.map +1 -0
  116. package/dist/policy-service.cjs +1190 -0
  117. package/dist/policy-service.cjs.map +1 -0
  118. package/dist/policy-service.d.cts +559 -0
  119. package/dist/policy-service.d.ts +2 -2
  120. package/dist/policy-service.js +239 -239
  121. package/dist/policy-service.js.map +1 -1
  122. package/dist/rbac/index.cjs +9 -0
  123. package/dist/rbac/index.d.cts +3 -0
  124. package/dist/rbac/index.d.ts +3 -0
  125. package/dist/rbac/index.js +2 -0
  126. package/dist/requested-by.cjs +54 -0
  127. package/dist/requested-by.cjs.map +1 -0
  128. package/dist/requested-by.d.cts +25 -0
  129. package/dist/requested-by.d.ts +25 -0
  130. package/dist/requested-by.js +49 -0
  131. package/dist/requested-by.js.map +1 -0
  132. package/dist/result/index.cjs +7 -0
  133. package/dist/result/index.d.cts +3 -0
  134. package/dist/result/index.d.ts +2 -1
  135. package/dist/result.cjs +135 -0
  136. package/dist/result.cjs.map +1 -0
  137. package/dist/result.d.cts +84 -0
  138. package/dist/result.d.ts +84 -0
  139. package/dist/rule.cjs +327 -0
  140. package/dist/rule.cjs.map +1 -0
  141. package/dist/rule.js +298 -0
  142. package/dist/rule.js.map +1 -0
  143. package/dist/ruleset-registry.cjs +47 -0
  144. package/dist/ruleset-registry.cjs.map +1 -0
  145. package/dist/rulesets/index.cjs +3 -0
  146. package/dist/rulesets/index.d.cts +2 -0
  147. package/dist/temporal-guards.cjs +32 -0
  148. package/dist/temporal-guards.cjs.map +1 -0
  149. package/dist/temporal-use-case.cjs +139 -0
  150. package/dist/temporal-use-case.cjs.map +1 -0
  151. package/dist/temporal-use-case.d.cts +282 -0
  152. package/dist/temporal-use-case.d.ts +5 -27
  153. package/dist/temporal-use-case.js +2 -48
  154. package/dist/temporal-use-case.js.map +1 -1
  155. package/dist/unexpected-error.cjs +19 -0
  156. package/dist/unexpected-error.cjs.map +1 -0
  157. package/dist/unexpected-error.js +2 -167
  158. package/dist/unexpected-error.js.map +1 -1
  159. package/dist/use-case.cjs +96 -0
  160. package/dist/use-case.cjs.map +1 -0
  161. package/dist/uuid-identifier.cjs +65 -0
  162. package/dist/uuid-identifier.cjs.map +1 -0
  163. package/dist/uuid-identifier.d.cts +147 -0
  164. package/dist/uuid-identifier.d.ts +2 -85
  165. package/dist/validation-code.cjs +60 -0
  166. package/dist/validation-code.cjs.map +1 -0
  167. package/dist/validation-code.d.cts +23 -0
  168. package/dist/validation-error.cjs +887 -0
  169. package/dist/validation-error.cjs.map +1 -0
  170. package/dist/validation-error.d.cts +681 -0
  171. package/dist/validation-error.d.ts +2 -98
  172. package/dist/validation-error.js +7 -134
  173. package/dist/validation-error.js.map +1 -1
  174. package/dist/validation-exception.cjs +41 -0
  175. package/dist/validation-exception.cjs.map +1 -0
  176. package/dist/validation-exception.d.cts +50 -0
  177. package/dist/validation-field.cjs +28 -0
  178. package/dist/validation-field.cjs.map +1 -0
  179. package/dist/validation-field.d.cts +12 -0
  180. package/dist/value-object-ruleset.contracts.d.cts +36 -0
  181. package/dist/value-object.cjs +85 -0
  182. package/dist/value-object.cjs.map +1 -0
  183. package/dist/value-object.d.cts +89 -0
  184. package/dist/value-object.d.ts +89 -0
  185. package/dist/value-object.js +1 -112
  186. package/dist/value-object.js.map +1 -1
  187. package/dist/version.d.cts +10 -0
  188. package/dist/versioning/index.cjs +7 -0
  189. package/dist/versioning/index.d.cts +3 -0
  190. package/meta.json +19 -4
  191. package/package.json +173 -28
  192. package/src/abac/index.ts +1 -0
  193. package/src/core/abac/abac-request.ts +29 -0
  194. package/src/core/abac/attributes.ts +39 -0
  195. package/src/core/abac/authorizer.ts +108 -0
  196. package/src/core/abac/combining.ts +63 -0
  197. package/src/core/abac/composite-authorizer.ts +45 -0
  198. package/src/core/abac/domain/policy-set.ts +52 -0
  199. package/src/core/abac/domain/rule.ts +197 -0
  200. package/src/core/abac/index.ts +21 -0
  201. package/src/core/application/ports/abac-authorizer.port.ts +20 -0
  202. package/src/core/application/ports/authorizer.port.ts +22 -0
  203. package/src/core/errors/authorization-error.ts +26 -0
  204. package/src/core/errors/error-codes.ts +1 -0
  205. package/src/core/index.ts +7 -0
  206. package/src/core/rbac/access-request.ts +32 -0
  207. package/src/core/rbac/authorizer.ts +91 -0
  208. package/src/core/rbac/domain/grant.ts +96 -0
  209. package/src/core/rbac/domain/permission-set.ts +67 -0
  210. package/src/core/rbac/domain/permission.ts +119 -0
  211. package/src/core/rbac/domain/role.ts +101 -0
  212. package/src/core/rbac/domain/scope.ts +84 -0
  213. package/src/core/rbac/index.ts +15 -0
  214. package/src/core/rbac/policy-bridge.ts +44 -0
  215. package/src/examples/application/authorize-cancel-order-abac.example.ts +107 -0
  216. package/src/examples/application/authorize-cancel-order.example.ts +101 -0
  217. package/src/rbac/index.ts +1 -0
  218. package/src/version.ts +1 -1
@@ -1,12 +1,248 @@
1
- import { t as UnexpectedError } from "./unexpected-error.js";
2
1
  import { n as sha256Hex, r as stableStringify } from "./hashing.js";
2
+ import { t as UnexpectedError } from "./unexpected-error.js";
3
3
  import { t as ValidationCode } from "./validation-code.js";
4
4
  import { t as ValidationField } from "./validation-field.js";
5
5
  import { t as InvariantViolationException } from "./invariant-violation-exception.js";
6
6
  import { t as InvalidValueException } from "./validation-exception.js";
7
7
  import { r as isValidDate } from "./temporal-guards.js";
8
- import { l as SilentPolicyReporter, o as PolicyContextPath } from "./gate-v1-payload.schema.js";
9
8
  import { r as Result } from "./result.js";
9
+ import { a as SilentPolicyReporter, n as PolicyContextPath } from "./condition-evaluator.js";
10
+ //#region src/core/policies/context/context-builder.ts
11
+ const DEFAULT_RESOLVER_RETRY_OPTIONS = {
12
+ maxAttempts: 1,
13
+ initialDelayMs: 25,
14
+ maxDelayMs: 1e3,
15
+ backoffMultiplier: 2
16
+ };
17
+ /**
18
+ * Builds a context object by resolving required paths from a seed.
19
+ */
20
+ var PolicyContextBuilder = class PolicyContextBuilder {
21
+ constructor(registry, options = {}) {
22
+ this.registry = registry;
23
+ this.circuitStates = /* @__PURE__ */ new Map();
24
+ this.defaultResolverResilience = options.defaultResolverResilience;
25
+ this.now = options.now ?? Date.now;
26
+ this.sleep = options.sleep ?? (async (delayMs) => {
27
+ if (delayMs <= 0) return;
28
+ await new Promise((resolve) => {
29
+ setTimeout(resolve, delayMs);
30
+ });
31
+ });
32
+ }
33
+ static clampPositiveInteger(value, fallback) {
34
+ if (value === void 0 || !Number.isFinite(value)) return fallback;
35
+ return Math.max(1, Math.trunc(value));
36
+ }
37
+ static clampNonNegativeInteger(value, fallback) {
38
+ if (value === void 0 || !Number.isFinite(value)) return fallback;
39
+ return Math.max(0, Math.trunc(value));
40
+ }
41
+ static normalizeTimeoutMs(value) {
42
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) return null;
43
+ return Math.trunc(value);
44
+ }
45
+ static mergeRetryOptions(defaults, overrides) {
46
+ if (overrides === false) return false;
47
+ if (overrides !== void 0) return {
48
+ ...defaults === false || defaults === void 0 ? {} : defaults,
49
+ ...overrides
50
+ };
51
+ return defaults;
52
+ }
53
+ static mergeCircuitBreakerOptions(defaults, overrides) {
54
+ if (overrides === false) return false;
55
+ if (overrides !== void 0) return {
56
+ ...defaults === false || defaults === void 0 ? {} : defaults,
57
+ ...overrides
58
+ };
59
+ return defaults;
60
+ }
61
+ resolveResilienceOptions(resolver) {
62
+ const defaults = this.defaultResolverResilience;
63
+ const override = resolver.resilience;
64
+ const retry = PolicyContextBuilder.mergeRetryOptions(defaults?.retry, override?.retry);
65
+ const circuitBreaker = PolicyContextBuilder.mergeCircuitBreakerOptions(defaults?.circuitBreaker, override?.circuitBreaker);
66
+ const retryOptions = retry === false ? void 0 : retry;
67
+ const circuitBreakerOptions = circuitBreaker === false ? void 0 : circuitBreaker;
68
+ return {
69
+ timeoutMs: PolicyContextBuilder.normalizeTimeoutMs(override?.timeoutMs ?? defaults?.timeoutMs),
70
+ retry: {
71
+ maxAttempts: PolicyContextBuilder.clampPositiveInteger(retryOptions?.maxAttempts, DEFAULT_RESOLVER_RETRY_OPTIONS.maxAttempts),
72
+ initialDelayMs: PolicyContextBuilder.clampNonNegativeInteger(retryOptions?.initialDelayMs, DEFAULT_RESOLVER_RETRY_OPTIONS.initialDelayMs),
73
+ maxDelayMs: PolicyContextBuilder.clampNonNegativeInteger(retryOptions?.maxDelayMs, DEFAULT_RESOLVER_RETRY_OPTIONS.maxDelayMs),
74
+ backoffMultiplier: PolicyContextBuilder.clampPositiveInteger(retryOptions?.backoffMultiplier, DEFAULT_RESOLVER_RETRY_OPTIONS.backoffMultiplier)
75
+ },
76
+ circuitBreaker: circuitBreakerOptions === void 0 ? null : {
77
+ failureThreshold: PolicyContextBuilder.clampPositiveInteger(circuitBreakerOptions.failureThreshold, 5),
78
+ cooldownMs: PolicyContextBuilder.clampPositiveInteger(circuitBreakerOptions.cooldownMs, 3e4)
79
+ }
80
+ };
81
+ }
82
+ getRetryDelayMs(attempt, retry) {
83
+ return Math.min(retry.initialDelayMs * retry.backoffMultiplier ** (attempt - 1), retry.maxDelayMs);
84
+ }
85
+ resetCircuit(path) {
86
+ this.circuitStates.delete(path);
87
+ }
88
+ isCircuitOpen(path, circuitBreaker) {
89
+ if (circuitBreaker === null) return false;
90
+ const state = this.circuitStates.get(path);
91
+ if (state === void 0 || state.openUntilMs === null) return false;
92
+ if (state.openUntilMs <= this.now()) {
93
+ this.resetCircuit(path);
94
+ return false;
95
+ }
96
+ return true;
97
+ }
98
+ recordResolverFailure(path, circuitBreaker) {
99
+ if (circuitBreaker === null) return;
100
+ const nextFailureCount = (this.circuitStates.get(path)?.failureCount ?? 0) + 1;
101
+ if (nextFailureCount >= circuitBreaker.failureThreshold) {
102
+ this.circuitStates.set(path, {
103
+ failureCount: 0,
104
+ openUntilMs: this.now() + circuitBreaker.cooldownMs
105
+ });
106
+ return;
107
+ }
108
+ this.circuitStates.set(path, {
109
+ failureCount: nextFailureCount,
110
+ openUntilMs: null
111
+ });
112
+ }
113
+ static describeThrownResolverError(error) {
114
+ if (error instanceof Error) return `threw ${error.name}: ${error.message}`;
115
+ return `threw ${String(error)}`;
116
+ }
117
+ async resolveOnce(resolver, seed, timeoutMs) {
118
+ const execute = async () => {
119
+ try {
120
+ return await resolver.resolve(seed);
121
+ } catch (error) {
122
+ return Result.err(PolicyContextBuilder.describeThrownResolverError(error));
123
+ }
124
+ };
125
+ if (timeoutMs === null) return execute();
126
+ let timeoutId;
127
+ try {
128
+ return await Promise.race([execute(), new Promise((resolve) => {
129
+ timeoutId = setTimeout(() => {
130
+ resolve(Result.err(`timed out after ${timeoutMs}ms`));
131
+ }, timeoutMs);
132
+ })]);
133
+ } finally {
134
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
135
+ }
136
+ }
137
+ async resolveWithResilience(path, resolver, seed) {
138
+ const resilience = this.resolveResilienceOptions(resolver);
139
+ if (this.isCircuitOpen(path, resilience.circuitBreaker)) return Result.err("circuit breaker is open");
140
+ let lastError = "resolver failed";
141
+ for (let attempt = 1; attempt <= resilience.retry.maxAttempts; attempt++) {
142
+ const result = await this.resolveOnce(resolver, seed, resilience.timeoutMs);
143
+ if (result.isOk()) {
144
+ this.resetCircuit(path);
145
+ return result;
146
+ }
147
+ lastError = result.errorOrNull() ?? lastError;
148
+ if (attempt < resilience.retry.maxAttempts) await this.sleep(this.getRetryDelayMs(attempt, resilience.retry));
149
+ }
150
+ this.recordResolverFailure(path, resilience.circuitBreaker);
151
+ return Result.err(lastError);
152
+ }
153
+ /**
154
+ * Resolves all required context paths and returns a flat context object.
155
+ * If any required path cannot be resolved, returns an error.
156
+ */
157
+ async build(requirements, seed) {
158
+ const context = {};
159
+ for (const path of requirements) {
160
+ const resolver = this.registry.get(path);
161
+ if (!resolver) return Result.err(`Missing resolver for path "${path}"`);
162
+ const result = await this.resolveWithResilience(path, resolver, seed);
163
+ if (result.isErr()) return Result.err(`Resolver error for path "${path}": ${result.errorOrNull()}`);
164
+ const setPathResult = PolicyContextPath.set(context, path, result.getOrNull());
165
+ if (setPathResult.isErr()) return Result.err(setPathResult.errorOrNull());
166
+ }
167
+ const missing = requirements.filter((path) => {
168
+ const result = PolicyContextPath.getOrAbsent(context, path);
169
+ return result.isErr() || result.getOrNull() === void 0;
170
+ });
171
+ if (missing.length > 0) return Result.err(`Context missing required paths after resolution: ${missing.join(", ")}`);
172
+ return Result.ok(context);
173
+ }
174
+ };
175
+ //#endregion
176
+ //#region src/core/policies/context/context-registry.ts
177
+ const DEFAULT_CONTEXT_RESOLVER_NAMESPACE = "default";
178
+ function normalizeNamespace(namespace) {
179
+ const normalized = namespace?.trim();
180
+ if (normalized === void 0 || normalized.length === 0) return DEFAULT_CONTEXT_RESOLVER_NAMESPACE;
181
+ return normalized;
182
+ }
183
+ /**
184
+ * Registry of context value resolvers, keyed by path.
185
+ */
186
+ var ContextResolverRegistry = class {
187
+ constructor() {
188
+ this.resolvers = /* @__PURE__ */ new Map();
189
+ }
190
+ register(resolver, options = {}) {
191
+ const namespace = normalizeNamespace(options.namespace);
192
+ const existing = this.resolvers.get(resolver.path);
193
+ if (existing !== void 0 && existing.namespace !== namespace) return Result.err(`Resolver path "${resolver.path}" is already registered by namespace "${existing.namespace}" and cannot be replaced by "${namespace}".`);
194
+ this.resolvers.set(resolver.path, {
195
+ namespace,
196
+ resolver
197
+ });
198
+ return Result.ok(void 0);
199
+ }
200
+ registerAll(resolvers, options = {}) {
201
+ for (const r of resolvers) {
202
+ const registrationResult = this.register(r, options);
203
+ if (registrationResult.isErr()) return registrationResult;
204
+ }
205
+ return Result.ok(void 0);
206
+ }
207
+ get(path) {
208
+ return this.resolvers.get(path)?.resolver;
209
+ }
210
+ has(path) {
211
+ return this.resolvers.has(path);
212
+ }
213
+ getNamespace(path) {
214
+ return this.resolvers.get(path)?.namespace;
215
+ }
216
+ };
217
+ function registerNamespacedContextResolversIn(registry, namespace, resolvers) {
218
+ const registrationResult = registry.registerAll(resolvers, { namespace });
219
+ if (registrationResult.isErr()) return Result.err(registrationResult.errorOrNull());
220
+ return Result.ok(registry);
221
+ }
222
+ //#endregion
223
+ //#region src/core/policies/context/context-registry.instance.ts
224
+ /**
225
+ * Official instance of the ContextResolverRegistry.
226
+ * Use `new ContextResolverRegistry()` plus `registerNamespacedContextResolversIn`
227
+ * when each runtime/composition root needs its own isolated resolver graph.
228
+ */
229
+ const contextResolverRegistry = new ContextResolverRegistry();
230
+ function registerNamespacedContextResolvers(namespace, resolvers) {
231
+ return registerNamespacedContextResolversIn(contextResolverRegistry, namespace, resolvers);
232
+ }
233
+ //#endregion
234
+ //#region src/core/policies/context/context-seed.ts
235
+ var ContextSeedValidator = class ContextSeedValidator {
236
+ static isBlankSeedId(value) {
237
+ return value.trim().length === 0;
238
+ }
239
+ static validate(seed) {
240
+ if (ContextSeedValidator.isBlankSeedId(seed.tenantId)) return Result.err("ContextSeed tenantId must be a non-empty string");
241
+ if (ContextSeedValidator.isBlankSeedId(seed.schoolId)) return Result.err("ContextSeed schoolId must be a non-empty string");
242
+ return Result.ok(seed);
243
+ }
244
+ };
245
+ //#endregion
10
246
  //#region src/core/policies/utils/hash.ts
11
247
  /**
12
248
  * Produces a deterministic SHA-256 hex digest of a canonical JSON representation.
@@ -487,242 +723,6 @@ var PolicyDefinition = class PolicyDefinition {
487
723
  }
488
724
  };
489
725
  //#endregion
490
- //#region src/core/policies/context/context-builder.ts
491
- const DEFAULT_RESOLVER_RETRY_OPTIONS = {
492
- maxAttempts: 1,
493
- initialDelayMs: 25,
494
- maxDelayMs: 1e3,
495
- backoffMultiplier: 2
496
- };
497
- /**
498
- * Builds a context object by resolving required paths from a seed.
499
- */
500
- var PolicyContextBuilder = class PolicyContextBuilder {
501
- constructor(registry, options = {}) {
502
- this.registry = registry;
503
- this.circuitStates = /* @__PURE__ */ new Map();
504
- this.defaultResolverResilience = options.defaultResolverResilience;
505
- this.now = options.now ?? Date.now;
506
- this.sleep = options.sleep ?? (async (delayMs) => {
507
- if (delayMs <= 0) return;
508
- await new Promise((resolve) => {
509
- setTimeout(resolve, delayMs);
510
- });
511
- });
512
- }
513
- static clampPositiveInteger(value, fallback) {
514
- if (value === void 0 || !Number.isFinite(value)) return fallback;
515
- return Math.max(1, Math.trunc(value));
516
- }
517
- static clampNonNegativeInteger(value, fallback) {
518
- if (value === void 0 || !Number.isFinite(value)) return fallback;
519
- return Math.max(0, Math.trunc(value));
520
- }
521
- static normalizeTimeoutMs(value) {
522
- if (value === void 0 || !Number.isFinite(value) || value <= 0) return null;
523
- return Math.trunc(value);
524
- }
525
- static mergeRetryOptions(defaults, overrides) {
526
- if (overrides === false) return false;
527
- if (overrides !== void 0) return {
528
- ...defaults === false || defaults === void 0 ? {} : defaults,
529
- ...overrides
530
- };
531
- return defaults;
532
- }
533
- static mergeCircuitBreakerOptions(defaults, overrides) {
534
- if (overrides === false) return false;
535
- if (overrides !== void 0) return {
536
- ...defaults === false || defaults === void 0 ? {} : defaults,
537
- ...overrides
538
- };
539
- return defaults;
540
- }
541
- resolveResilienceOptions(resolver) {
542
- const defaults = this.defaultResolverResilience;
543
- const override = resolver.resilience;
544
- const retry = PolicyContextBuilder.mergeRetryOptions(defaults?.retry, override?.retry);
545
- const circuitBreaker = PolicyContextBuilder.mergeCircuitBreakerOptions(defaults?.circuitBreaker, override?.circuitBreaker);
546
- const retryOptions = retry === false ? void 0 : retry;
547
- const circuitBreakerOptions = circuitBreaker === false ? void 0 : circuitBreaker;
548
- return {
549
- timeoutMs: PolicyContextBuilder.normalizeTimeoutMs(override?.timeoutMs ?? defaults?.timeoutMs),
550
- retry: {
551
- maxAttempts: PolicyContextBuilder.clampPositiveInteger(retryOptions?.maxAttempts, DEFAULT_RESOLVER_RETRY_OPTIONS.maxAttempts),
552
- initialDelayMs: PolicyContextBuilder.clampNonNegativeInteger(retryOptions?.initialDelayMs, DEFAULT_RESOLVER_RETRY_OPTIONS.initialDelayMs),
553
- maxDelayMs: PolicyContextBuilder.clampNonNegativeInteger(retryOptions?.maxDelayMs, DEFAULT_RESOLVER_RETRY_OPTIONS.maxDelayMs),
554
- backoffMultiplier: PolicyContextBuilder.clampPositiveInteger(retryOptions?.backoffMultiplier, DEFAULT_RESOLVER_RETRY_OPTIONS.backoffMultiplier)
555
- },
556
- circuitBreaker: circuitBreakerOptions === void 0 ? null : {
557
- failureThreshold: PolicyContextBuilder.clampPositiveInteger(circuitBreakerOptions.failureThreshold, 5),
558
- cooldownMs: PolicyContextBuilder.clampPositiveInteger(circuitBreakerOptions.cooldownMs, 3e4)
559
- }
560
- };
561
- }
562
- getRetryDelayMs(attempt, retry) {
563
- return Math.min(retry.initialDelayMs * retry.backoffMultiplier ** (attempt - 1), retry.maxDelayMs);
564
- }
565
- resetCircuit(path) {
566
- this.circuitStates.delete(path);
567
- }
568
- isCircuitOpen(path, circuitBreaker) {
569
- if (circuitBreaker === null) return false;
570
- const state = this.circuitStates.get(path);
571
- if (state === void 0 || state.openUntilMs === null) return false;
572
- if (state.openUntilMs <= this.now()) {
573
- this.resetCircuit(path);
574
- return false;
575
- }
576
- return true;
577
- }
578
- recordResolverFailure(path, circuitBreaker) {
579
- if (circuitBreaker === null) return;
580
- const nextFailureCount = (this.circuitStates.get(path)?.failureCount ?? 0) + 1;
581
- if (nextFailureCount >= circuitBreaker.failureThreshold) {
582
- this.circuitStates.set(path, {
583
- failureCount: 0,
584
- openUntilMs: this.now() + circuitBreaker.cooldownMs
585
- });
586
- return;
587
- }
588
- this.circuitStates.set(path, {
589
- failureCount: nextFailureCount,
590
- openUntilMs: null
591
- });
592
- }
593
- static describeThrownResolverError(error) {
594
- if (error instanceof Error) return `threw ${error.name}: ${error.message}`;
595
- return `threw ${String(error)}`;
596
- }
597
- async resolveOnce(resolver, seed, timeoutMs) {
598
- const execute = async () => {
599
- try {
600
- return await resolver.resolve(seed);
601
- } catch (error) {
602
- return Result.err(PolicyContextBuilder.describeThrownResolverError(error));
603
- }
604
- };
605
- if (timeoutMs === null) return execute();
606
- let timeoutId;
607
- try {
608
- return await Promise.race([execute(), new Promise((resolve) => {
609
- timeoutId = setTimeout(() => {
610
- resolve(Result.err(`timed out after ${timeoutMs}ms`));
611
- }, timeoutMs);
612
- })]);
613
- } finally {
614
- if (timeoutId !== void 0) clearTimeout(timeoutId);
615
- }
616
- }
617
- async resolveWithResilience(path, resolver, seed) {
618
- const resilience = this.resolveResilienceOptions(resolver);
619
- if (this.isCircuitOpen(path, resilience.circuitBreaker)) return Result.err("circuit breaker is open");
620
- let lastError = "resolver failed";
621
- for (let attempt = 1; attempt <= resilience.retry.maxAttempts; attempt++) {
622
- const result = await this.resolveOnce(resolver, seed, resilience.timeoutMs);
623
- if (result.isOk()) {
624
- this.resetCircuit(path);
625
- return result;
626
- }
627
- lastError = result.errorOrNull() ?? lastError;
628
- if (attempt < resilience.retry.maxAttempts) await this.sleep(this.getRetryDelayMs(attempt, resilience.retry));
629
- }
630
- this.recordResolverFailure(path, resilience.circuitBreaker);
631
- return Result.err(lastError);
632
- }
633
- /**
634
- * Resolves all required context paths and returns a flat context object.
635
- * If any required path cannot be resolved, returns an error.
636
- */
637
- async build(requirements, seed) {
638
- const context = {};
639
- for (const path of requirements) {
640
- const resolver = this.registry.get(path);
641
- if (!resolver) return Result.err(`Missing resolver for path "${path}"`);
642
- const result = await this.resolveWithResilience(path, resolver, seed);
643
- if (result.isErr()) return Result.err(`Resolver error for path "${path}": ${result.errorOrNull()}`);
644
- const setPathResult = PolicyContextPath.set(context, path, result.getOrNull());
645
- if (setPathResult.isErr()) return Result.err(setPathResult.errorOrNull());
646
- }
647
- const missing = requirements.filter((path) => {
648
- const result = PolicyContextPath.getOrAbsent(context, path);
649
- return result.isErr() || result.getOrNull() === void 0;
650
- });
651
- if (missing.length > 0) return Result.err(`Context missing required paths after resolution: ${missing.join(", ")}`);
652
- return Result.ok(context);
653
- }
654
- };
655
- //#endregion
656
- //#region src/core/policies/context/context-registry.ts
657
- const DEFAULT_CONTEXT_RESOLVER_NAMESPACE = "default";
658
- function normalizeNamespace(namespace) {
659
- const normalized = namespace?.trim();
660
- if (normalized === void 0 || normalized.length === 0) return DEFAULT_CONTEXT_RESOLVER_NAMESPACE;
661
- return normalized;
662
- }
663
- /**
664
- * Registry of context value resolvers, keyed by path.
665
- */
666
- var ContextResolverRegistry = class {
667
- constructor() {
668
- this.resolvers = /* @__PURE__ */ new Map();
669
- }
670
- register(resolver, options = {}) {
671
- const namespace = normalizeNamespace(options.namespace);
672
- const existing = this.resolvers.get(resolver.path);
673
- if (existing !== void 0 && existing.namespace !== namespace) return Result.err(`Resolver path "${resolver.path}" is already registered by namespace "${existing.namespace}" and cannot be replaced by "${namespace}".`);
674
- this.resolvers.set(resolver.path, {
675
- namespace,
676
- resolver
677
- });
678
- return Result.ok(void 0);
679
- }
680
- registerAll(resolvers, options = {}) {
681
- for (const r of resolvers) {
682
- const registrationResult = this.register(r, options);
683
- if (registrationResult.isErr()) return registrationResult;
684
- }
685
- return Result.ok(void 0);
686
- }
687
- get(path) {
688
- return this.resolvers.get(path)?.resolver;
689
- }
690
- has(path) {
691
- return this.resolvers.has(path);
692
- }
693
- getNamespace(path) {
694
- return this.resolvers.get(path)?.namespace;
695
- }
696
- };
697
- function registerNamespacedContextResolversIn(registry, namespace, resolvers) {
698
- const registrationResult = registry.registerAll(resolvers, { namespace });
699
- if (registrationResult.isErr()) return Result.err(registrationResult.errorOrNull());
700
- return Result.ok(registry);
701
- }
702
- //#endregion
703
- //#region src/core/policies/context/context-registry.instance.ts
704
- /**
705
- * Official instance of the ContextResolverRegistry.
706
- * Use `new ContextResolverRegistry()` plus `registerNamespacedContextResolversIn`
707
- * when each runtime/composition root needs its own isolated resolver graph.
708
- */
709
- const contextResolverRegistry = new ContextResolverRegistry();
710
- function registerNamespacedContextResolvers(namespace, resolvers) {
711
- return registerNamespacedContextResolversIn(contextResolverRegistry, namespace, resolvers);
712
- }
713
- //#endregion
714
- //#region src/core/policies/context/context-seed.ts
715
- var ContextSeedValidator = class ContextSeedValidator {
716
- static isBlankSeedId(value) {
717
- return value.trim().length === 0;
718
- }
719
- static validate(seed) {
720
- if (ContextSeedValidator.isBlankSeedId(seed.tenantId)) return Result.err("ContextSeed tenantId must be a non-empty string");
721
- if (ContextSeedValidator.isBlankSeedId(seed.schoolId)) return Result.err("ContextSeed schoolId must be a non-empty string");
722
- return Result.ok(seed);
723
- }
724
- };
725
- //#endregion
726
726
  //#region src/core/policies/asof/asof.ts
727
727
  const DEFAULT_MAX_AS_OF_FUTURE_YEARS = 10;
728
728
  var PolicyAsOfResolver = class PolicyAsOfResolver {
@@ -1060,6 +1060,6 @@ var PolicyService = class {
1060
1060
  }
1061
1061
  };
1062
1062
  //#endregion
1063
- export { asPolicyDecisionId as _, ContextSeedValidator as a, asTenantId as b, ContextResolverRegistry as c, PolicyDefinition as d, InMemoryPolicyDefinitionRepository as f, PolicyCatalog as g, PolicyCatalogFactory as h, PolicyAsOfResolver as i, registerNamespacedContextResolversIn as l, PolicyKey as m, PolicyEvaluationErrors as n, contextResolverRegistry as o, PolicyScopeMatcher as p, PolicyResolver as r, registerNamespacedContextResolvers as s, PolicyService as t, PolicyContextBuilder as u, asPolicyDefinitionId as v, PolicyHashing as x, asSchoolId as y };
1063
+ export { contextResolverRegistry as _, PolicyDefinition as a, registerNamespacedContextResolversIn as b, PolicyKey as c, asPolicyDecisionId as d, asPolicyDefinitionId as f, ContextSeedValidator as g, PolicyHashing as h, PolicyAsOfResolver as i, PolicyCatalogFactory as l, asTenantId as m, PolicyEvaluationErrors as n, InMemoryPolicyDefinitionRepository as o, asSchoolId as p, PolicyResolver as r, PolicyScopeMatcher as s, PolicyService as t, PolicyCatalog as u, registerNamespacedContextResolvers as v, PolicyContextBuilder as x, ContextResolverRegistry as y };
1064
1064
 
1065
1065
  //# sourceMappingURL=policy-service.js.map