@cullet/erp-core 1.3.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.
- package/KIT_CONTEXT.md +5 -3
- package/README.md +160 -0
- package/dist/abac/index.cjs +7 -0
- package/dist/abac/index.d.cts +2 -0
- package/dist/abac/index.d.ts +2 -0
- package/dist/abac/index.js +2 -0
- package/dist/app-error.cjs +193 -0
- package/dist/app-error.cjs.map +1 -0
- package/dist/app-error.js +170 -0
- package/dist/app-error.js.map +1 -0
- package/dist/application/index.cjs +2 -1
- package/dist/application/index.d.cts +2 -1
- package/dist/application/index.d.ts +2 -1
- package/dist/application/index.js +2 -1
- package/dist/authorization-error.cjs +157 -0
- package/dist/authorization-error.cjs.map +1 -0
- package/dist/authorization-error.d.cts +113 -0
- package/dist/authorization-error.d.ts +113 -0
- package/dist/authorization-error.js +152 -0
- package/dist/authorization-error.js.map +1 -0
- package/dist/authorizer.port.d.cts +130 -0
- package/dist/authorizer.port.d.ts +130 -0
- package/dist/composite-authorizer.d.cts +216 -0
- package/dist/composite-authorizer.d.ts +216 -0
- package/dist/condition-evaluator.cjs +565 -0
- package/dist/condition-evaluator.cjs.map +1 -0
- package/dist/condition-evaluator.js +536 -0
- package/dist/condition-evaluator.js.map +1 -0
- package/dist/{gate-types.d.ts → core-config.d.cts} +78 -77
- package/dist/{gate-types.d.cts → core-config.d.ts} +78 -77
- package/dist/domain/index.cjs +2 -1
- package/dist/domain/index.d.cts +2 -1
- package/dist/domain/index.d.ts +2 -1
- package/dist/domain/index.js +2 -1
- package/dist/domain-event-contracts.cjs +3 -2
- package/dist/domain-event-contracts.cjs.map +1 -1
- package/dist/domain-event-contracts.js +2 -1
- package/dist/domain-event-contracts.js.map +1 -1
- package/dist/entity.cjs +126 -0
- package/dist/entity.cjs.map +1 -0
- package/dist/entity.js +115 -0
- package/dist/entity.js.map +1 -0
- package/dist/errors/index.cjs +8 -6
- package/dist/errors/index.d.cts +2 -1
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +4 -2
- package/dist/gate-engine-registry.cjs +5 -4
- package/dist/gate-engine-registry.cjs.map +1 -1
- package/dist/gate-engine-registry.d.cts +3 -2
- package/dist/gate-engine-registry.d.ts +3 -2
- package/dist/gate-engine-registry.js +2 -1
- package/dist/gate-engine-registry.js.map +1 -1
- package/dist/gate-v1-payload.schema.cjs +0 -562
- package/dist/gate-v1-payload.schema.cjs.map +1 -1
- package/dist/gate-v1-payload.schema.js +1 -533
- package/dist/gate-v1-payload.schema.js.map +1 -1
- package/dist/index.cjs +34 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -12
- package/dist/index.d.ts +19 -12
- package/dist/index.js +15 -9
- package/dist/index.js.map +1 -1
- package/dist/not-found-error.cjs +5 -5
- package/dist/not-found-error.cjs.map +1 -1
- package/dist/not-found-error.js +1 -1
- package/dist/outcome.d.cts +1 -83
- package/dist/outcome.d.ts +1 -83
- package/dist/parse-gate-payload.d.cts +2 -2
- package/dist/parse-gate-payload.d.ts +2 -2
- package/dist/path.d.cts +2 -2
- package/dist/path.d.ts +2 -2
- package/dist/policies/engines/index.d.cts +1 -1
- package/dist/policies/engines/index.d.ts +1 -1
- package/dist/policies/engines/v1/gate/index.cjs +5 -4
- package/dist/policies/engines/v1/gate/index.cjs.map +1 -1
- package/dist/policies/engines/v1/gate/index.d.cts +2 -2
- package/dist/policies/engines/v1/gate/index.d.ts +2 -2
- package/dist/policies/engines/v1/gate/index.js +2 -1
- package/dist/policies/engines/v1/gate/index.js.map +1 -1
- package/dist/policies/index.cjs +5 -5
- package/dist/policies/index.d.cts +3 -2
- package/dist/policies/index.d.ts +3 -2
- package/dist/policies/index.js +2 -2
- package/dist/policy-bridge.cjs +437 -0
- package/dist/policy-bridge.cjs.map +1 -0
- package/dist/policy-bridge.d.cts +185 -0
- package/dist/policy-bridge.d.ts +185 -0
- package/dist/policy-bridge.js +396 -0
- package/dist/policy-bridge.js.map +1 -0
- package/dist/policy-service.cjs +239 -239
- package/dist/policy-service.cjs.map +1 -1
- package/dist/policy-service.d.cts +2 -2
- package/dist/policy-service.d.ts +2 -2
- package/dist/policy-service.js +239 -239
- package/dist/policy-service.js.map +1 -1
- package/dist/rbac/index.cjs +9 -0
- package/dist/rbac/index.d.cts +3 -0
- package/dist/rbac/index.d.ts +3 -0
- package/dist/rbac/index.js +2 -0
- package/dist/requested-by.cjs +54 -0
- package/dist/requested-by.cjs.map +1 -0
- package/dist/requested-by.d.cts +25 -0
- package/dist/requested-by.d.ts +25 -0
- package/dist/requested-by.js +49 -0
- package/dist/requested-by.js.map +1 -0
- package/dist/result/index.d.cts +2 -1
- package/dist/result/index.d.ts +2 -1
- package/dist/result.d.cts +84 -0
- package/dist/result.d.ts +84 -0
- package/dist/rule.cjs +327 -0
- package/dist/rule.cjs.map +1 -0
- package/dist/rule.js +298 -0
- package/dist/rule.js.map +1 -0
- package/dist/temporal-use-case.cjs +1 -53
- package/dist/temporal-use-case.cjs.map +1 -1
- package/dist/temporal-use-case.d.cts +5 -27
- package/dist/temporal-use-case.d.ts +5 -27
- package/dist/temporal-use-case.js +2 -48
- package/dist/temporal-use-case.js.map +1 -1
- package/dist/unexpected-error.cjs +3 -192
- package/dist/unexpected-error.cjs.map +1 -1
- package/dist/unexpected-error.js +2 -167
- package/dist/unexpected-error.js.map +1 -1
- package/dist/uuid-identifier.d.cts +2 -85
- package/dist/uuid-identifier.d.ts +2 -85
- package/dist/validation-error.cjs +33 -166
- package/dist/validation-error.cjs.map +1 -1
- package/dist/validation-error.d.cts +2 -98
- package/dist/validation-error.d.ts +2 -98
- package/dist/validation-error.js +7 -134
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.cjs +0 -123
- package/dist/value-object.cjs.map +1 -1
- package/dist/value-object.d.cts +89 -0
- package/dist/value-object.d.ts +89 -0
- package/dist/value-object.js +1 -112
- package/dist/value-object.js.map +1 -1
- package/meta.json +18 -4
- package/package.json +27 -1
- package/src/abac/index.ts +1 -0
- package/src/core/abac/abac-request.ts +29 -0
- package/src/core/abac/attributes.ts +39 -0
- package/src/core/abac/authorizer.ts +108 -0
- package/src/core/abac/combining.ts +63 -0
- package/src/core/abac/composite-authorizer.ts +45 -0
- package/src/core/abac/domain/policy-set.ts +52 -0
- package/src/core/abac/domain/rule.ts +197 -0
- package/src/core/abac/index.ts +21 -0
- package/src/core/application/ports/abac-authorizer.port.ts +20 -0
- package/src/core/application/ports/authorizer.port.ts +22 -0
- package/src/core/errors/authorization-error.ts +26 -0
- package/src/core/errors/error-codes.ts +1 -0
- package/src/core/index.ts +7 -0
- package/src/core/rbac/access-request.ts +32 -0
- package/src/core/rbac/authorizer.ts +91 -0
- package/src/core/rbac/domain/grant.ts +96 -0
- package/src/core/rbac/domain/permission-set.ts +67 -0
- package/src/core/rbac/domain/permission.ts +119 -0
- package/src/core/rbac/domain/role.ts +101 -0
- package/src/core/rbac/domain/scope.ts +84 -0
- package/src/core/rbac/index.ts +15 -0
- package/src/core/rbac/policy-bridge.ts +44 -0
- package/src/examples/application/authorize-cancel-order-abac.example.ts +107 -0
- package/src/examples/application/authorize-cancel-order.example.ts +101 -0
- package/src/rbac/index.ts +1 -0
- package/src/version.ts +1 -1
package/dist/policy-service.cjs
CHANGED
|
@@ -1,12 +1,248 @@
|
|
|
1
|
-
const require_unexpected_error = require("./unexpected-error.cjs");
|
|
2
1
|
const require_hashing = require("./hashing.cjs");
|
|
2
|
+
const require_unexpected_error = require("./unexpected-error.cjs");
|
|
3
3
|
const require_validation_code = require("./validation-code.cjs");
|
|
4
4
|
const require_validation_field = require("./validation-field.cjs");
|
|
5
5
|
const require_invariant_violation_exception = require("./invariant-violation-exception.cjs");
|
|
6
6
|
const require_validation_exception = require("./validation-exception.cjs");
|
|
7
7
|
const require_temporal_guards = require("./temporal-guards.cjs");
|
|
8
|
-
const require_gate_v1_payload_schema = require("./gate-v1-payload.schema.cjs");
|
|
9
8
|
const require_result = require("./result.cjs");
|
|
9
|
+
const require_condition_evaluator = require("./condition-evaluator.cjs");
|
|
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 require_result.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(require_result.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 require_result.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 require_result.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 require_result.Result.err(`Missing resolver for path "${path}"`);
|
|
162
|
+
const result = await this.resolveWithResilience(path, resolver, seed);
|
|
163
|
+
if (result.isErr()) return require_result.Result.err(`Resolver error for path "${path}": ${result.errorOrNull()}`);
|
|
164
|
+
const setPathResult = require_condition_evaluator.PolicyContextPath.set(context, path, result.getOrNull());
|
|
165
|
+
if (setPathResult.isErr()) return require_result.Result.err(setPathResult.errorOrNull());
|
|
166
|
+
}
|
|
167
|
+
const missing = requirements.filter((path) => {
|
|
168
|
+
const result = require_condition_evaluator.PolicyContextPath.getOrAbsent(context, path);
|
|
169
|
+
return result.isErr() || result.getOrNull() === void 0;
|
|
170
|
+
});
|
|
171
|
+
if (missing.length > 0) return require_result.Result.err(`Context missing required paths after resolution: ${missing.join(", ")}`);
|
|
172
|
+
return require_result.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 require_result.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 require_result.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 require_result.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 require_result.Result.err(registrationResult.errorOrNull());
|
|
220
|
+
return require_result.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 require_result.Result.err("ContextSeed tenantId must be a non-empty string");
|
|
241
|
+
if (ContextSeedValidator.isBlankSeedId(seed.schoolId)) return require_result.Result.err("ContextSeed schoolId must be a non-empty string");
|
|
242
|
+
return require_result.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 require_result.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(require_result.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 require_result.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 require_result.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 require_result.Result.err(`Missing resolver for path "${path}"`);
|
|
642
|
-
const result = await this.resolveWithResilience(path, resolver, seed);
|
|
643
|
-
if (result.isErr()) return require_result.Result.err(`Resolver error for path "${path}": ${result.errorOrNull()}`);
|
|
644
|
-
const setPathResult = require_gate_v1_payload_schema.PolicyContextPath.set(context, path, result.getOrNull());
|
|
645
|
-
if (setPathResult.isErr()) return require_result.Result.err(setPathResult.errorOrNull());
|
|
646
|
-
}
|
|
647
|
-
const missing = requirements.filter((path) => {
|
|
648
|
-
const result = require_gate_v1_payload_schema.PolicyContextPath.getOrAbsent(context, path);
|
|
649
|
-
return result.isErr() || result.getOrNull() === void 0;
|
|
650
|
-
});
|
|
651
|
-
if (missing.length > 0) return require_result.Result.err(`Context missing required paths after resolution: ${missing.join(", ")}`);
|
|
652
|
-
return require_result.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 require_result.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 require_result.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 require_result.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 require_result.Result.err(registrationResult.errorOrNull());
|
|
700
|
-
return require_result.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 require_result.Result.err("ContextSeed tenantId must be a non-empty string");
|
|
721
|
-
if (ContextSeedValidator.isBlankSeedId(seed.schoolId)) return require_result.Result.err("ContextSeed schoolId must be a non-empty string");
|
|
722
|
-
return require_result.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 {
|
|
@@ -875,7 +875,7 @@ var PolicyService = class {
|
|
|
875
875
|
this.gateEngines = params.gateEngines;
|
|
876
876
|
this.computeRegistry = params.computeRegistry;
|
|
877
877
|
this.options = params.options ?? {};
|
|
878
|
-
this.#reporter = this.options.reporter ?? new
|
|
878
|
+
this.#reporter = this.options.reporter ?? new require_condition_evaluator.SilentPolicyReporter();
|
|
879
879
|
}
|
|
880
880
|
#reportSafely(event) {
|
|
881
881
|
try {
|