@cullet/erp-core 1.1.0 → 1.3.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 (196) hide show
  1. package/KIT_CONTEXT.md +1 -67
  2. package/dist/app-error.d.cts +108 -0
  3. package/dist/application/index.cjs +26 -0
  4. package/dist/application/index.d.cts +2 -0
  5. package/dist/application/index.d.ts +1 -1
  6. package/dist/application/index.js +2 -1
  7. package/dist/domain/index.cjs +16 -0
  8. package/dist/domain/index.d.cts +3 -0
  9. package/dist/domain/index.d.ts +3 -0
  10. package/dist/domain/index.js +3 -0
  11. package/dist/domain-event-contracts.cjs +33 -0
  12. package/dist/domain-event-contracts.cjs.map +1 -0
  13. package/dist/domain-event-contracts.d.cts +27 -0
  14. package/dist/domain-event-contracts.d.ts +27 -0
  15. package/dist/domain-event-contracts.js +22 -0
  16. package/dist/domain-event-contracts.js.map +1 -0
  17. package/dist/domain-exception.cjs +17 -0
  18. package/dist/domain-exception.cjs.map +1 -0
  19. package/dist/domain-exception.d.cts +7 -0
  20. package/dist/domain-exception.d.ts +7 -0
  21. package/dist/domain-exception.js +12 -0
  22. package/dist/domain-exception.js.map +1 -0
  23. package/dist/errors/index.cjs +41 -0
  24. package/dist/errors/index.d.cts +3 -0
  25. package/dist/errors/index.js +1 -1
  26. package/dist/exceptions/index.cjs +17 -0
  27. package/dist/exceptions/index.d.cts +5 -0
  28. package/dist/exceptions/index.d.ts +5 -0
  29. package/dist/exceptions/index.js +7 -0
  30. package/dist/exceptions/validation-field.cjs +3 -0
  31. package/dist/exceptions/validation-field.d.cts +2 -0
  32. package/dist/gate-engine-registry.cjs +308 -0
  33. package/dist/gate-engine-registry.cjs.map +1 -0
  34. package/dist/gate-engine-registry.d.cts +81 -0
  35. package/dist/gate-engine-registry.d.ts +2 -1
  36. package/dist/gate-engine-registry.js +2 -1
  37. package/dist/gate-engine-registry.js.map +1 -1
  38. package/dist/gate-types.d.cts +171 -0
  39. package/dist/gate-types.d.ts +3 -139
  40. package/dist/gate-v1-payload.schema.cjs +638 -0
  41. package/dist/gate-v1-payload.schema.cjs.map +1 -0
  42. package/dist/gate-v1-payload.schema.js +2 -116
  43. package/dist/gate-v1-payload.schema.js.map +1 -1
  44. package/dist/hashing.cjs +66 -0
  45. package/dist/hashing.cjs.map +1 -0
  46. package/dist/immutable.cjs +77 -0
  47. package/dist/immutable.cjs.map +1 -0
  48. package/dist/immutable.d.cts +6 -0
  49. package/dist/immutable.d.ts +6 -0
  50. package/dist/immutable.js +54 -0
  51. package/dist/immutable.js.map +1 -0
  52. package/dist/index.cjs +163 -0
  53. package/dist/index.cjs.map +1 -0
  54. package/dist/index.d.cts +30 -0
  55. package/dist/index.d.ts +15 -160
  56. package/dist/index.js +19 -176
  57. package/dist/index.js.map +1 -1
  58. package/dist/invalid-state-transition-exception.cjs +47 -0
  59. package/dist/invalid-state-transition-exception.cjs.map +1 -0
  60. package/dist/invalid-state-transition-exception.js +30 -0
  61. package/dist/invalid-state-transition-exception.js.map +1 -0
  62. package/dist/invariant-violation-exception.cjs +16 -0
  63. package/dist/invariant-violation-exception.cjs.map +1 -0
  64. package/dist/invariant-violation-exception.js +11 -0
  65. package/dist/invariant-violation-exception.js.map +1 -0
  66. package/dist/not-found-error.cjs +65 -0
  67. package/dist/not-found-error.cjs.map +1 -0
  68. package/dist/not-found-error.js +1 -1
  69. package/dist/outcome.cjs +97 -0
  70. package/dist/outcome.cjs.map +1 -0
  71. package/dist/outcome.d.cts +140 -0
  72. package/dist/outcome.d.ts +140 -0
  73. package/dist/outcome.js +1 -1
  74. package/dist/parse-gate-payload.d.cts +62 -0
  75. package/dist/parse-gate-payload.d.ts +2 -1
  76. package/dist/path.d.cts +90 -0
  77. package/dist/path.d.ts +2 -1
  78. package/dist/plugin.cjs +79 -0
  79. package/dist/plugin.cjs.map +1 -0
  80. package/dist/plugin.d.cts +85 -0
  81. package/dist/plugin.d.ts +85 -0
  82. package/dist/plugin.js +74 -0
  83. package/dist/plugin.js.map +1 -0
  84. package/dist/plugins/index.cjs +3 -0
  85. package/dist/plugins/index.d.cts +2 -0
  86. package/dist/plugins/index.d.ts +2 -0
  87. package/dist/plugins/index.js +2 -0
  88. package/dist/policies/engines/index.cjs +10 -0
  89. package/dist/policies/engines/index.d.cts +4 -0
  90. package/dist/policies/engines/index.d.ts +1 -1
  91. package/dist/policies/engines/v1/gate/index.cjs +91 -0
  92. package/dist/policies/engines/v1/gate/index.cjs.map +1 -0
  93. package/dist/policies/engines/v1/gate/index.d.cts +121 -0
  94. package/dist/policies/engines/v1/gate/index.d.ts +2 -1
  95. package/dist/policies/engines/v1/gate/index.js +2 -1
  96. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  97. package/dist/policies/index.cjs +41 -0
  98. package/dist/policies/index.d.cts +7 -0
  99. package/dist/policies/index.d.ts +2 -1
  100. package/dist/policies/index.js +2 -1
  101. package/dist/policy-service.cjs +1190 -0
  102. package/dist/policy-service.cjs.map +1 -0
  103. package/dist/policy-service.d.cts +559 -0
  104. package/dist/policy-service.d.ts +2 -1
  105. package/dist/policy-service.js +6 -3
  106. package/dist/policy-service.js.map +1 -1
  107. package/dist/result/index.cjs +7 -0
  108. package/dist/result/index.d.cts +2 -0
  109. package/dist/result/index.d.ts +2 -0
  110. package/dist/result/index.js +3 -0
  111. package/dist/result.cjs +135 -0
  112. package/dist/result.cjs.map +1 -0
  113. package/dist/result.js +118 -0
  114. package/dist/result.js.map +1 -0
  115. package/dist/ruleset-registry.cjs +47 -0
  116. package/dist/ruleset-registry.cjs.map +1 -0
  117. package/dist/ruleset-registry.js +42 -0
  118. package/dist/ruleset-registry.js.map +1 -0
  119. package/dist/rulesets/index.cjs +3 -0
  120. package/dist/rulesets/index.d.cts +2 -0
  121. package/dist/rulesets/index.d.ts +2 -0
  122. package/dist/rulesets/index.js +2 -0
  123. package/dist/temporal-guards.cjs +32 -0
  124. package/dist/temporal-guards.cjs.map +1 -0
  125. package/dist/temporal-guards.js +2 -17
  126. package/dist/temporal-guards.js.map +1 -1
  127. package/dist/temporal-use-case.cjs +191 -0
  128. package/dist/temporal-use-case.cjs.map +1 -0
  129. package/dist/temporal-use-case.d.cts +304 -0
  130. package/dist/temporal-use-case.d.ts +4 -9
  131. package/dist/temporal-use-case.js +6 -140
  132. package/dist/temporal-use-case.js.map +1 -1
  133. package/dist/unexpected-error.cjs +208 -0
  134. package/dist/unexpected-error.cjs.map +1 -0
  135. package/dist/unexpected-error.js +179 -0
  136. package/dist/unexpected-error.js.map +1 -0
  137. package/dist/use-case.cjs +96 -0
  138. package/dist/use-case.cjs.map +1 -0
  139. package/dist/use-case.js +91 -0
  140. package/dist/use-case.js.map +1 -0
  141. package/dist/uuid-identifier.cjs +65 -0
  142. package/dist/uuid-identifier.cjs.map +1 -0
  143. package/dist/uuid-identifier.d.cts +230 -0
  144. package/dist/uuid-identifier.d.ts +230 -0
  145. package/dist/uuid-identifier.js +60 -0
  146. package/dist/uuid-identifier.js.map +1 -0
  147. package/dist/validation-code.cjs +60 -0
  148. package/dist/validation-code.cjs.map +1 -0
  149. package/dist/validation-code.d.cts +23 -0
  150. package/dist/validation-code.d.ts +23 -0
  151. package/dist/validation-code.js +1 -177
  152. package/dist/validation-code.js.map +1 -1
  153. package/dist/validation-error.cjs +1020 -0
  154. package/dist/validation-error.cjs.map +1 -0
  155. package/dist/validation-error.d.cts +777 -0
  156. package/dist/validation-error.d.ts +1 -21
  157. package/dist/validation-error.js +1 -1
  158. package/dist/validation-exception.cjs +41 -0
  159. package/dist/validation-exception.cjs.map +1 -0
  160. package/dist/validation-exception.d.cts +50 -0
  161. package/dist/validation-exception.d.ts +50 -0
  162. package/dist/validation-exception.js +8 -2
  163. package/dist/validation-exception.js.map +1 -1
  164. package/dist/validation-field.cjs +28 -0
  165. package/dist/validation-field.cjs.map +1 -0
  166. package/dist/validation-field.d.cts +12 -0
  167. package/dist/value-object-ruleset.contracts.d.cts +36 -0
  168. package/dist/value-object-ruleset.contracts.d.ts +36 -0
  169. package/dist/value-object.cjs +208 -0
  170. package/dist/value-object.cjs.map +1 -0
  171. package/dist/value-object.js +191 -0
  172. package/dist/value-object.js.map +1 -0
  173. package/dist/version.d.cts +10 -0
  174. package/dist/version.d.ts +10 -0
  175. package/dist/versioning/index.cjs +7 -0
  176. package/dist/versioning/index.d.cts +3 -0
  177. package/dist/versioning/index.d.ts +3 -0
  178. package/dist/versioning/index.js +3 -0
  179. package/meta.json +18 -3
  180. package/package.json +165 -17
  181. package/src/core/domain/rulesets/index.ts +7 -0
  182. package/src/core/domain/uuid-identifier.ts +72 -0
  183. package/src/core/domain/value-object.ts +37 -5
  184. package/src/core/exceptions/index.ts +13 -0
  185. package/src/core/index.ts +14 -2
  186. package/src/core/plugins/index.ts +7 -0
  187. package/src/core/plugins/plugin.ts +99 -0
  188. package/src/core/plugins/types.ts +53 -0
  189. package/src/core/versioning/index.ts +14 -0
  190. package/src/domain/index.ts +7 -0
  191. package/src/exceptions/index.ts +1 -0
  192. package/src/plugins/index.ts +1 -0
  193. package/src/result/index.ts +2 -0
  194. package/src/rulesets/index.ts +1 -0
  195. package/src/version.ts +1 -1
  196. package/src/versioning/index.ts +1 -0
@@ -0,0 +1,1190 @@
1
+ const require_unexpected_error = require("./unexpected-error.cjs");
2
+ const require_hashing = require("./hashing.cjs");
3
+ const require_validation_code = require("./validation-code.cjs");
4
+ const require_validation_field = require("./validation-field.cjs");
5
+ const require_invariant_violation_exception = require("./invariant-violation-exception.cjs");
6
+ const require_validation_exception = require("./validation-exception.cjs");
7
+ const require_temporal_guards = require("./temporal-guards.cjs");
8
+ const require_gate_v1_payload_schema = require("./gate-v1-payload.schema.cjs");
9
+ const require_result = require("./result.cjs");
10
+ //#region src/core/policies/utils/hash.ts
11
+ /**
12
+ * Produces a deterministic SHA-256 hex digest of a canonical JSON representation.
13
+ * Object keys are sorted recursively so key order does not affect the hash.
14
+ * Array item order is preserved on purpose; callers that treat arrays as sets
15
+ * must normalize or sort them before hashing.
16
+ *
17
+ * Values that JSON.stringify would silently drop or coerce (undefined,
18
+ * non-finite numbers, functions, symbols, bigint) are rejected up-front to
19
+ * prevent semantically different payloads from collapsing to the same hash.
20
+ */
21
+ var PolicyHashing = class PolicyHashing {
22
+ static sha256(input) {
23
+ return require_hashing.sha256Hex(input);
24
+ }
25
+ static assertHashable(value, path, seen) {
26
+ if (value === null) return;
27
+ const type = typeof value;
28
+ if (type === "undefined") throw new TypeError(`canonicalJson does not accept undefined values (at ${path})`);
29
+ if (type === "number" && !Number.isFinite(value)) throw new TypeError(`canonicalJson does not accept non-finite numbers (at ${path}, value: ${String(value)})`);
30
+ if (type === "bigint" || type === "function" || type === "symbol") throw new TypeError(`canonicalJson does not accept ${type} values (at ${path})`);
31
+ if (type !== "object") return;
32
+ if (value instanceof Date) {
33
+ if (!require_temporal_guards.isValidDate(value)) throw new TypeError(`canonicalJson does not accept Invalid Date (at ${path})`);
34
+ return;
35
+ }
36
+ if (seen.has(value)) throw new TypeError(`canonicalJson does not accept circular references (at ${path})`);
37
+ seen.add(value);
38
+ if (Array.isArray(value)) value.forEach((item, index) => {
39
+ PolicyHashing.assertHashable(item, `${path}[${index}]`, seen);
40
+ });
41
+ else for (const [key, nested] of Object.entries(value)) PolicyHashing.assertHashable(nested, `${path}.${key}`, seen);
42
+ seen.delete(value);
43
+ }
44
+ static canonicalJson(value) {
45
+ PolicyHashing.assertHashable(value, "$", /* @__PURE__ */ new WeakSet());
46
+ return require_hashing.stableStringify(value);
47
+ }
48
+ static computePayloadHash(payload, policyKey, policyVersion) {
49
+ const canonical = PolicyHashing.canonicalJson({
50
+ policyKey,
51
+ policyVersion,
52
+ payload
53
+ });
54
+ return PolicyHashing.sha256(canonical);
55
+ }
56
+ };
57
+ //#endregion
58
+ //#region src/core/policies/policy-ids.ts
59
+ const POLICY_DEFINITION_ID_FIELD = require_validation_field.ValidationField.of("policyDefinitionId");
60
+ const POLICY_DECISION_ID_FIELD = require_validation_field.ValidationField.of("policyDecisionId");
61
+ const TENANT_ID_FIELD = require_validation_field.ValidationField.of("tenantId");
62
+ const SCHOOL_ID_FIELD = require_validation_field.ValidationField.of("schoolId");
63
+ function assertIdString(value, field) {
64
+ if (typeof value !== "string") throw new require_validation_exception.InvalidValueException(field, require_validation_code.ValidationCode.INVALID_FORMAT, `${field.value} must be a string, got ${typeof value}`);
65
+ if (value.trim().length === 0) throw new require_validation_exception.InvalidValueException(field, require_validation_code.ValidationCode.BLANK, `${field.value} must be a non-empty string`);
66
+ return value;
67
+ }
68
+ function brandId(value, field) {
69
+ return assertIdString(value, field);
70
+ }
71
+ function asPolicyDefinitionId(value) {
72
+ return brandId(value, POLICY_DEFINITION_ID_FIELD);
73
+ }
74
+ function asPolicyDecisionId(value) {
75
+ return brandId(value, POLICY_DECISION_ID_FIELD);
76
+ }
77
+ function asTenantId(value) {
78
+ return brandId(value, TENANT_ID_FIELD);
79
+ }
80
+ function asSchoolId(value) {
81
+ return brandId(value, SCHOOL_ID_FIELD);
82
+ }
83
+ //#endregion
84
+ //#region src/core/policies/catalog/policy-catalog-entry.ts
85
+ /**
86
+ * Represents a single entry in the Policy Catalog.
87
+ * Encapsulates metadata and validation logic for a policy.
88
+ */
89
+ var PolicyCatalogEntry = class PolicyCatalogEntry {
90
+ constructor(props) {
91
+ this.key = props.key;
92
+ this.kind = props.kind;
93
+ this.gateEngineVersion = props.gateEngineVersion;
94
+ this.computeEngineVersion = props.computeEngineVersion;
95
+ this.payloadSchemaVersion = props.payloadSchemaVersion;
96
+ this.owner = props.owner;
97
+ this.allowedScopes = [...props.allowedScopes];
98
+ this.asOfSource = props.asOfSource;
99
+ this.contextRequirements = [...props.contextRequirements];
100
+ this.tags = [...props.tags ?? []];
101
+ this.description = props.description;
102
+ }
103
+ static assertFamilyConsistency(entries) {
104
+ if (entries.length === 0) throw new require_unexpected_error.UnexpectedError("Cannot validate an empty PolicyCatalogEntry family");
105
+ const [first, ...rest] = entries;
106
+ for (const entry of rest) {
107
+ if (!first.key.equals(entry.key)) throw new require_unexpected_error.UnexpectedError("Cannot merge PolicyCatalog entries with different keys");
108
+ if (first.kind !== entry.kind) throw new require_unexpected_error.UnexpectedError(`PolicyCatalog entries for key "${first.key.toString()}" must share the same kind`);
109
+ if (first.owner !== entry.owner) throw new require_unexpected_error.UnexpectedError(`PolicyCatalog entries for key "${first.key.toString()}" must share the same owner`);
110
+ if (first.asOfSource !== entry.asOfSource) throw new require_unexpected_error.UnexpectedError(`PolicyCatalog entries for key "${first.key.toString()}" must share the same asOfSource`);
111
+ if (first.description !== entry.description) throw new require_unexpected_error.UnexpectedError(`PolicyCatalog entries for key "${first.key.toString()}" must share the same description`);
112
+ }
113
+ }
114
+ static from(entry) {
115
+ return entry instanceof PolicyCatalogEntry ? entry : new PolicyCatalogEntry(entry);
116
+ }
117
+ isGate() {
118
+ return this.kind === "GATE";
119
+ }
120
+ isCompute() {
121
+ return this.kind === "COMPUTE";
122
+ }
123
+ allowsScope(scope) {
124
+ return this.allowedScopes.includes(scope);
125
+ }
126
+ hasExplicitVersionSelector() {
127
+ return this.gateEngineVersion !== void 0 || this.computeEngineVersion !== void 0 || this.payloadSchemaVersion !== void 0;
128
+ }
129
+ matchesVersion(params) {
130
+ if (this.kind !== params.kind) return false;
131
+ if (this.gateEngineVersion !== void 0 && this.gateEngineVersion !== params.gateEngineVersion) return false;
132
+ if (this.computeEngineVersion !== void 0 && this.computeEngineVersion !== params.computeEngineVersion) return false;
133
+ if (this.payloadSchemaVersion !== void 0 && this.payloadSchemaVersion !== params.payloadSchemaVersion) return false;
134
+ return true;
135
+ }
136
+ toVariantKey() {
137
+ return [
138
+ this.key.toString(),
139
+ this.kind,
140
+ this.gateEngineVersion ?? "gate:any",
141
+ this.computeEngineVersion ?? "compute:any",
142
+ this.payloadSchemaVersion ?? "schema:any"
143
+ ].join("::");
144
+ }
145
+ usesNowAsOf() {
146
+ return this.asOfSource === "NOW";
147
+ }
148
+ usesCallerProvidedAsOf() {
149
+ return this.asOfSource === "CALLER_PROVIDED";
150
+ }
151
+ requiresContext(path) {
152
+ return this.contextRequirements.includes(path);
153
+ }
154
+ requiresAllContexts(paths) {
155
+ return paths.every((path) => this.requiresContext(path));
156
+ }
157
+ hasTag(tag) {
158
+ return this.tags.includes(tag);
159
+ }
160
+ equals(other) {
161
+ return this.key.equals(other.key);
162
+ }
163
+ toJSON() {
164
+ return {
165
+ key: this.key,
166
+ kind: this.kind,
167
+ ...this.gateEngineVersion !== void 0 ? { gateEngineVersion: this.gateEngineVersion } : {},
168
+ ...this.computeEngineVersion !== void 0 ? { computeEngineVersion: this.computeEngineVersion } : {},
169
+ ...this.payloadSchemaVersion !== void 0 ? { payloadSchemaVersion: this.payloadSchemaVersion } : {},
170
+ owner: this.owner,
171
+ allowedScopes: [...this.allowedScopes],
172
+ asOfSource: this.asOfSource,
173
+ contextRequirements: [...this.contextRequirements],
174
+ tags: this.tags.length > 0 ? [...this.tags] : void 0,
175
+ description: this.description
176
+ };
177
+ }
178
+ };
179
+ //#endregion
180
+ //#region src/core/policies/catalog/policy-catalog.ts
181
+ /**
182
+ * In-code catalog of all known policies.
183
+ * The catalog defines the universe of policies; the database cannot invent new ones.
184
+ */
185
+ var PolicyCatalog = class {
186
+ /**
187
+ * Indexes the given entries up front into two lookups — one per exact
188
+ * variant, one per policy key (the "family") — and validates them eagerly:
189
+ * a duplicate variant or an internally inconsistent family throws here, at
190
+ * wiring time, so a malformed catalog fails fast at startup rather than on
191
+ * the first evaluation.
192
+ *
193
+ * @throws {UnexpectedError} When two entries resolve to the same variant key.
194
+ */
195
+ constructor(entries) {
196
+ const families = /* @__PURE__ */ new Map();
197
+ const versionedMap = /* @__PURE__ */ new Map();
198
+ for (const rawEntry of entries) {
199
+ const entry = PolicyCatalogEntry.from(rawEntry);
200
+ const key = entry.key.toString();
201
+ const variantKey = entry.toVariantKey();
202
+ if (versionedMap.has(variantKey)) throw new require_unexpected_error.UnexpectedError(`Duplicate PolicyCatalog entry for key "${key}" and variant "${variantKey}"`);
203
+ const family = families.get(key) ?? [];
204
+ family.push(entry);
205
+ families.set(key, family);
206
+ versionedMap.set(variantKey, entry);
207
+ }
208
+ for (const family of families.values()) PolicyCatalogEntry.assertFamilyConsistency(family);
209
+ this.versionedEntries = versionedMap;
210
+ this.entriesByKey = families;
211
+ }
212
+ /**
213
+ * Returns the single entry for a key. Deliberately strict: if the key has
214
+ * more than one variant it errs rather than guessing, steering the caller to
215
+ * {@link getVersioned} or {@link getFamily}. Use this only when a key is
216
+ * known to be unambiguous.
217
+ *
218
+ * @returns `ok` with the entry, or `err` when the key is unknown or ambiguous.
219
+ */
220
+ get(key) {
221
+ const familyResult = this.getFamily(key);
222
+ if (familyResult.isErr()) return require_result.Result.err(familyResult.errorOrNull());
223
+ const family = familyResult.getOrNull();
224
+ if (family.length > 1) return require_result.Result.err(`Policy key "${key}" has multiple catalog variants; use getVersioned(...) or getFamily(...)`);
225
+ const [entry] = family;
226
+ if (!entry) return require_result.Result.err(`Policy not found in catalog: "${key}"`);
227
+ return require_result.Result.ok(entry);
228
+ }
229
+ /**
230
+ * Returns every variant registered under a key — the whole "family". This is
231
+ * the lookup the evaluation pipeline uses, since a single key may host
232
+ * several engine/schema variants that the resolver then chooses between.
233
+ *
234
+ * @returns `ok` with the family (always non-empty), or `err` when the key is unknown.
235
+ */
236
+ getFamily(key) {
237
+ const family = this.entriesByKey.get(key);
238
+ if (!family) return require_result.Result.err(`Policy not found in catalog: "${key}"`);
239
+ return require_result.Result.ok(family);
240
+ }
241
+ /**
242
+ * Resolves the one entry matching an exact variant — kind plus engine and
243
+ * payload-schema versions. Falls back to the sole family member when a key
244
+ * has exactly one entry and that entry declares no explicit version
245
+ * selector, so unversioned single-variant policies "just work" without the
246
+ * caller spelling out versions.
247
+ *
248
+ * @param params - The key and the variant coordinates to match on.
249
+ * @returns `ok` with the matching entry, or `err` when no variant matches.
250
+ */
251
+ getVersioned(params) {
252
+ const family = this.entriesByKey.get(params.key);
253
+ if (!family || family.length === 0) return require_result.Result.err(`Policy not found in catalog: "${params.key}"`);
254
+ const exactMatch = family.find((entry) => entry.matchesVersion(params));
255
+ if (exactMatch) return require_result.Result.ok(exactMatch);
256
+ if (family.length === 1 && !family[0].hasExplicitVersionSelector()) return require_result.Result.ok(family[0]);
257
+ const versionDetails = [
258
+ params.kind,
259
+ params.gateEngineVersion !== void 0 ? `gateEngineVersion=${params.gateEngineVersion}` : null,
260
+ params.computeEngineVersion !== void 0 ? `computeEngineVersion=${params.computeEngineVersion}` : null,
261
+ params.payloadSchemaVersion !== void 0 ? `payloadSchemaVersion=${params.payloadSchemaVersion}` : null
262
+ ].filter((part) => part !== null).join(", ");
263
+ return require_result.Result.err(`Policy variant not found in catalog: "${params.key}" (${versionDetails})`);
264
+ }
265
+ /** Lists every registered variant across all keys — useful for introspection and diagnostics. */
266
+ list() {
267
+ return Array.from(this.versionedEntries.values());
268
+ }
269
+ /** Returns whether any variant is registered under the given key. */
270
+ has(key) {
271
+ return this.entriesByKey.has(key);
272
+ }
273
+ };
274
+ //#endregion
275
+ //#region src/core/policies/catalog/catalog.instance.ts
276
+ /**
277
+ * Aggregates catalog entries contributed by each module package into a single
278
+ * PolicyCatalog. The core knows nothing about concrete modules — callers
279
+ * are responsible for passing all relevant packages at composition time.
280
+ */
281
+ var PolicyCatalogFactory = class {
282
+ static build(packages) {
283
+ return new PolicyCatalog(packages.flatMap((policyPackage) => Array.from(policyPackage.catalogEntries).map(PolicyCatalogEntry.from)));
284
+ }
285
+ };
286
+ //#endregion
287
+ //#region src/core/policies/catalog/policy-key.ts
288
+ /**
289
+ * PolicyKey value object.
290
+ *
291
+ * Format: `module.area.policy_name` (exactly 3 dot-separated segments).
292
+ * - module: structural segment (lowercase alphanumeric + underscore, starting with a letter)
293
+ * - area: structural segment (lowercase alphanumeric + underscore, starting with a letter)
294
+ * - name: specific policy identifier (lowercase alphanumeric + underscore, starting with a letter)
295
+ */
296
+ const SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
297
+ const MAX_POLICY_KEY_LENGTH = 255;
298
+ var PolicyKey = class PolicyKey {
299
+ constructor(module, area, name) {
300
+ this.module = module;
301
+ this.area = area;
302
+ this.name = name;
303
+ Object.freeze(this);
304
+ }
305
+ static parse(raw) {
306
+ if (!raw || typeof raw !== "string") return require_result.Result.err("PolicyKey cannot be empty");
307
+ if (raw.length > MAX_POLICY_KEY_LENGTH) return require_result.Result.err(`PolicyKey cannot exceed ${MAX_POLICY_KEY_LENGTH} characters, got ${raw.length}`);
308
+ const segments = raw.split(".");
309
+ if (segments.length !== 3) return require_result.Result.err(`PolicyKey must have exactly 3 segments (module.area.name), got ${segments.length}: "${raw}"`);
310
+ const [rawModule, rawArea, name] = segments;
311
+ if (!SEGMENT_PATTERN.test(rawModule)) return require_result.Result.err(`PolicyKey segment "module" is invalid: "${rawModule}". Must start with a lowercase letter and contain only [a-z0-9_].`);
312
+ if (!SEGMENT_PATTERN.test(rawArea)) return require_result.Result.err(`PolicyKey segment "area" is invalid: "${rawArea}". Must start with a lowercase letter and contain only [a-z0-9_].`);
313
+ if (!SEGMENT_PATTERN.test(name)) return require_result.Result.err(`PolicyKey segment "name" is invalid: "${name}". Must start with a lowercase letter and contain only [a-z0-9_].`);
314
+ return require_result.Result.ok(new PolicyKey(rawModule, rawArea, name));
315
+ }
316
+ toString() {
317
+ return `${this.module}.${this.area}.${this.name}`;
318
+ }
319
+ equals(other) {
320
+ return this.toString() === other.toString();
321
+ }
322
+ };
323
+ //#endregion
324
+ //#region src/core/policies/defs/policy-scope.ts
325
+ /**
326
+ * Numeric weight for scope specificity (higher = more specific).
327
+ */
328
+ const SCOPE_WEIGHT = {
329
+ SCHOOL: 3,
330
+ TENANT: 2,
331
+ GLOBAL: 1
332
+ };
333
+ var PolicyScopeMatcher = class PolicyScopeMatcher {
334
+ static weight(level) {
335
+ return SCOPE_WEIGHT[level];
336
+ }
337
+ static matchesChain(defScope, chain) {
338
+ return chain.some((scope) => PolicyScopeMatcher.equals(defScope, scope));
339
+ }
340
+ static equals(a, b) {
341
+ return a.level === b.level && a.tenantId === b.tenantId && a.schoolId === b.schoolId;
342
+ }
343
+ };
344
+ //#endregion
345
+ //#region src/core/policies/defs/in-memory-policy-definition-repo.ts
346
+ var InMemoryPolicyDefinitionRepository = class {
347
+ constructor(definitions) {
348
+ this.definitions = definitions;
349
+ this.definitionsByPolicyKey = definitions.reduce((index, definition) => {
350
+ const existingDefinitions = index.get(definition.policyKey) ?? [];
351
+ existingDefinitions.push(definition);
352
+ index.set(definition.policyKey, existingDefinitions);
353
+ return index;
354
+ }, /* @__PURE__ */ new Map());
355
+ }
356
+ findCandidates(params) {
357
+ const { policyKey, kind, payloadSchemaVersion, asOf, contextVersion, scopeChain } = params;
358
+ return (this.definitionsByPolicyKey.get(policyKey) ?? []).filter((def) => {
359
+ if (def.kind !== kind) return false;
360
+ if (payloadSchemaVersion !== void 0 && def.payloadSchemaVersion !== payloadSchemaVersion) return false;
361
+ if (!def.enabled) return false;
362
+ if (def.status !== "PUBLISHED") return false;
363
+ if (def.effectiveFrom > asOf) return false;
364
+ if (def.effectiveTo !== null && def.effectiveTo <= asOf) return false;
365
+ if (contextVersion < def.contextVersionMin) return false;
366
+ if (contextVersion > def.contextVersionMax) return false;
367
+ if (!PolicyScopeMatcher.matchesChain(def.scope, scopeChain)) return false;
368
+ return true;
369
+ });
370
+ }
371
+ };
372
+ //#endregion
373
+ //#region src/core/policies/defs/policy-semver.ts
374
+ const POLICY_SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
375
+ function parseSemver(version) {
376
+ const withoutBuild = version.split("+")[0];
377
+ const dashIndex = withoutBuild.indexOf("-");
378
+ const core = dashIndex === -1 ? withoutBuild : withoutBuild.slice(0, dashIndex);
379
+ const preRelease = dashIndex === -1 ? null : withoutBuild.slice(dashIndex + 1);
380
+ const [major, minor, patch] = core.split(".").map(Number);
381
+ return {
382
+ major,
383
+ minor,
384
+ patch,
385
+ preRelease
386
+ };
387
+ }
388
+ function comparePreRelease(a, b) {
389
+ const aIds = a.split(".");
390
+ const bIds = b.split(".");
391
+ const len = Math.max(aIds.length, bIds.length);
392
+ for (let i = 0; i < len; i++) {
393
+ const ai = aIds[i];
394
+ const bi = bIds[i];
395
+ if (ai === void 0) return -1;
396
+ if (bi === void 0) return 1;
397
+ const aNum = Number(ai);
398
+ const bNum = Number(bi);
399
+ const aIsNum = !Number.isNaN(aNum);
400
+ const bIsNum = !Number.isNaN(bNum);
401
+ if (aIsNum && !bIsNum) return -1;
402
+ if (!aIsNum && bIsNum) return 1;
403
+ if (aIsNum && bIsNum) {
404
+ if (aNum !== bNum) return aNum - bNum;
405
+ } else {
406
+ if (ai < bi) return -1;
407
+ if (ai > bi) return 1;
408
+ }
409
+ }
410
+ return 0;
411
+ }
412
+ /**
413
+ * Compares two valid semver strings.
414
+ * Returns a negative number if a < b, 0 if a === b, positive if a > b.
415
+ *
416
+ * Callers are expected to pass only strings that already passed
417
+ * `POLICY_SEMVER_PATTERN` validation (i.e. values stored in PolicyDefinition).
418
+ */
419
+ function comparePolicySemver(a, b) {
420
+ const av = parseSemver(a);
421
+ const bv = parseSemver(b);
422
+ if (av.major !== bv.major) return av.major - bv.major;
423
+ if (av.minor !== bv.minor) return av.minor - bv.minor;
424
+ if (av.patch !== bv.patch) return av.patch - bv.patch;
425
+ if (av.preRelease === null && bv.preRelease !== null) return 1;
426
+ if (av.preRelease !== null && bv.preRelease === null) return -1;
427
+ if (av.preRelease !== null && bv.preRelease !== null) return comparePreRelease(av.preRelease, bv.preRelease);
428
+ return 0;
429
+ }
430
+ //#endregion
431
+ //#region src/core/policies/defs/policy-definition.ts
432
+ var PolicyDefinition = class PolicyDefinition {
433
+ static assertPolicyVersionIsSemver(policyVersion) {
434
+ if (!POLICY_SEMVER_PATTERN.test(policyVersion)) throw new require_invariant_violation_exception.InvariantViolationException(`PolicyDefinition.policyVersion must be a valid semver. Received: "${policyVersion}"`);
435
+ }
436
+ static assertCoherentBounds(props) {
437
+ if (props.contextVersionMin > props.contextVersionMax) throw new require_invariant_violation_exception.InvariantViolationException(`PolicyDefinition.contextVersionMin (${props.contextVersionMin}) must not exceed contextVersionMax (${props.contextVersionMax}). Policy key: ${props.policyKey}`);
438
+ if (props.effectiveTo !== null && props.effectiveTo.getTime() <= props.effectiveFrom.getTime()) throw new require_invariant_violation_exception.InvariantViolationException(`PolicyDefinition.effectiveTo must be later than effectiveFrom. Policy key: ${props.policyKey}`);
439
+ }
440
+ constructor(props) {
441
+ this.id = props.id;
442
+ this.policyKey = props.policyKey;
443
+ PolicyDefinition.assertPolicyVersionIsSemver(props.policyVersion);
444
+ PolicyDefinition.assertCoherentBounds(props);
445
+ this.policyVersion = props.policyVersion;
446
+ this.payloadSchemaVersion = props.payloadSchemaVersion;
447
+ this.contextVersionMin = props.contextVersionMin;
448
+ this.contextVersionMax = props.contextVersionMax;
449
+ this.enabled = props.enabled;
450
+ this.status = props.status;
451
+ this.scope = props.scope;
452
+ this.effectiveFrom = props.effectiveFrom;
453
+ this.effectiveTo = props.effectiveTo;
454
+ this.priority = props.priority;
455
+ this.payloadJson = props.payloadJson;
456
+ this.payloadHash = props.payloadHash;
457
+ this.createdAt = props.createdAt;
458
+ this.publishedAt = props.publishedAt;
459
+ this.kind = props.kind;
460
+ if (props.kind === "GATE") {
461
+ if (props.gateEngineVersion === void 0) throw new require_invariant_violation_exception.InvariantViolationException(`PolicyDefinition of kind 'GATE' must have a 'gateEngineVersion'. Policy key: ${props.policyKey}`);
462
+ this.gateEngineVersion = props.gateEngineVersion;
463
+ } else {
464
+ if (props.computeEngineVersion === void 0) throw new require_invariant_violation_exception.InvariantViolationException(`PolicyDefinition of kind 'COMPUTE' must have a 'computeEngineVersion'. Policy key: ${props.policyKey}`);
465
+ this.computeEngineVersion = props.computeEngineVersion;
466
+ }
467
+ }
468
+ static gate(props) {
469
+ return new PolicyDefinition({
470
+ ...props,
471
+ enabled: props.enabled ?? true,
472
+ kind: "GATE"
473
+ });
474
+ }
475
+ static compute(props) {
476
+ return new PolicyDefinition({
477
+ ...props,
478
+ enabled: props.enabled ?? true,
479
+ kind: "COMPUTE"
480
+ });
481
+ }
482
+ isGate() {
483
+ return this.kind === "GATE";
484
+ }
485
+ isCompute() {
486
+ return this.kind === "COMPUTE";
487
+ }
488
+ };
489
+ //#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
+ //#region src/core/policies/asof/asof.ts
727
+ const DEFAULT_MAX_AS_OF_FUTURE_YEARS = 10;
728
+ var PolicyAsOfResolver = class PolicyAsOfResolver {
729
+ static resolveMaxFutureYears(options) {
730
+ const maxFutureYears = options.maxFutureYears ?? DEFAULT_MAX_AS_OF_FUTURE_YEARS;
731
+ if (!Number.isInteger(maxFutureYears) || maxFutureYears < 0) return require_result.Result.err(`deriveAsOf maxFutureYears must be a non-negative integer. Received: ${maxFutureYears}`);
732
+ return require_result.Result.ok(maxFutureYears);
733
+ }
734
+ static derive(source, seed, now, options = {}) {
735
+ const maxFutureYearsResult = PolicyAsOfResolver.resolveMaxFutureYears(options);
736
+ if (maxFutureYearsResult.isErr()) return require_result.Result.err(maxFutureYearsResult.errorOrNull());
737
+ const maxFutureYears = maxFutureYearsResult.getOrNull();
738
+ switch (source) {
739
+ case "NOW": return require_result.Result.ok(now);
740
+ case "CALLER_PROVIDED": {
741
+ const asOf = seed.fields["asOf"];
742
+ if (!require_temporal_guards.isValidDate(asOf)) return require_result.Result.err("asOfSource \"CALLER_PROVIDED\" requires seed.fields.asOf to be a Date");
743
+ const maxFuture = new Date(now);
744
+ maxFuture.setFullYear(maxFuture.getFullYear() + maxFutureYears);
745
+ if (asOf > maxFuture) return require_result.Result.err(`asOf date is more than ${maxFutureYears} years in the future (received ${asOf.toISOString()})`);
746
+ return require_result.Result.ok(asOf);
747
+ }
748
+ default: return require_result.Result.err(`Unknown asOfSource: "${source}"`);
749
+ }
750
+ }
751
+ };
752
+ //#endregion
753
+ //#region src/core/policies/resolver/policy-resolver.ts
754
+ /**
755
+ * Selects the best policy definition from a list of already-filtered candidates.
756
+ *
757
+ * Ordering criteria (highest priority first):
758
+ * 1. Scope specificity: SCHOOL (3) > TENANT (2) > GLOBAL (1)
759
+ * 2. Priority: higher number wins
760
+ * 3. publishedAt descending (fallback to createdAt)
761
+ * 4. Engine version descending (prefer-latest-engine-version — deterministic tiebreaker)
762
+ * 5. policyVersion descending (semver — final deterministic tiebreaker)
763
+ */
764
+ var PolicyResolver = class {
765
+ /**
766
+ * Picks the single winning definition from an already-filtered candidate
767
+ * list by applying the class's ordering criteria in priority order. The sort
768
+ * runs over a copy, so the caller's array is left untouched, and the final
769
+ * semver tiebreaker guarantees the winner is fully deterministic — it never
770
+ * depends on the order the repository returned the candidates in.
771
+ *
772
+ * @param candidates - Definitions already filtered to match key, scope, and as-of.
773
+ * @returns `ok` with the highest-ranked definition, or `err` when the list is empty.
774
+ */
775
+ resolveBest(candidates) {
776
+ if (candidates.length === 0) return require_result.Result.err("No matching policy definition found (candidates list is empty)");
777
+ const sorted = [...candidates].sort((a, b) => {
778
+ const scopeDiff = PolicyScopeMatcher.weight(b.scope.level) - PolicyScopeMatcher.weight(a.scope.level);
779
+ if (scopeDiff !== 0) return scopeDiff;
780
+ const priorityDiff = b.priority - a.priority;
781
+ if (priorityDiff !== 0) return priorityDiff;
782
+ const aDate = a.publishedAt ?? a.createdAt;
783
+ const dateDiff = (b.publishedAt ?? b.createdAt).getTime() - aDate.getTime();
784
+ if (dateDiff !== 0) return dateDiff;
785
+ const aEngine = a.gateEngineVersion ?? a.computeEngineVersion ?? 0;
786
+ const engineDiff = (b.gateEngineVersion ?? b.computeEngineVersion ?? 0) - aEngine;
787
+ if (engineDiff !== 0) return engineDiff;
788
+ return comparePolicySemver(b.policyVersion, a.policyVersion);
789
+ });
790
+ return require_result.Result.ok(sorted[0]);
791
+ }
792
+ };
793
+ //#endregion
794
+ //#region src/core/policies/service/policy-evaluation-error.ts
795
+ var PolicyEvaluationErrors = class {
796
+ static invalidContext(stage, policyKey, cause) {
797
+ return {
798
+ kind: "INVALID_CONTEXT",
799
+ stage,
800
+ policyKey,
801
+ message: "Policy evaluation context is invalid",
802
+ cause
803
+ };
804
+ }
805
+ static invalidPolicyKey(rawPolicyKey, cause) {
806
+ return {
807
+ kind: "INVALID_POLICY_KEY",
808
+ rawPolicyKey,
809
+ message: "Policy key is invalid",
810
+ cause
811
+ };
812
+ }
813
+ static policyNotFound(policyKey, cause) {
814
+ return {
815
+ kind: "POLICY_NOT_FOUND",
816
+ policyKey,
817
+ message: "Policy is not registered in the catalog",
818
+ cause
819
+ };
820
+ }
821
+ static policyDefinitionNotFound(policyKey, asOf, contextVersion, cause) {
822
+ return {
823
+ kind: "POLICY_DEFINITION_NOT_FOUND",
824
+ policyKey,
825
+ contextVersion,
826
+ asOf,
827
+ message: "No published policy definition matched the evaluation inputs",
828
+ cause
829
+ };
830
+ }
831
+ static policyVariantNotFound(params) {
832
+ return {
833
+ kind: "POLICY_VARIANT_NOT_FOUND",
834
+ policyKey: params.policyKey,
835
+ policyKind: params.policyKind,
836
+ payloadSchemaVersion: params.payloadSchemaVersion,
837
+ ...params.gateEngineVersion !== void 0 ? { gateEngineVersion: params.gateEngineVersion } : {},
838
+ ...params.computeEngineVersion !== void 0 ? { computeEngineVersion: params.computeEngineVersion } : {},
839
+ message: "No catalog variant matched the selected policy definition",
840
+ cause: params.cause
841
+ };
842
+ }
843
+ static engineFailure(policyKey, engine, engineVersion, cause) {
844
+ return {
845
+ kind: "ENGINE_FAILURE",
846
+ policyKey,
847
+ engine,
848
+ engineVersion,
849
+ message: "Policy engine evaluation failed",
850
+ cause
851
+ };
852
+ }
853
+ };
854
+ //#endregion
855
+ //#region src/core/policies/service/policy-service.ts
856
+ /**
857
+ * Orchestrates the end-to-end evaluation of a policy: it parses and validates
858
+ * the context seed, derives the as-of instant, resolves the best matching
859
+ * definition for the requested key/scope/version, builds the context the policy
860
+ * needs, and runs it through the right engine (gate or compute).
861
+ *
862
+ * The service never throws for an expected failure: every step returns a
863
+ * {@link Result}, and the failures are folded into a typed
864
+ * {@link PolicyEvaluationError} so callers branch on `isErr()` rather than
865
+ * catching. Telemetry is best-effort — a throwing reporter can never abort an
866
+ * evaluation — which keeps observability strictly side-band.
867
+ */
868
+ var PolicyService = class {
869
+ #reporter;
870
+ constructor(params) {
871
+ this.catalog = params.catalog;
872
+ this.contextBuilder = params.contextBuilder;
873
+ this.defRepo = params.defRepo;
874
+ this.resolver = params.resolver;
875
+ this.gateEngines = params.gateEngines;
876
+ this.computeRegistry = params.computeRegistry;
877
+ this.options = params.options ?? {};
878
+ this.#reporter = this.options.reporter ?? new require_gate_v1_payload_schema.SilentPolicyReporter();
879
+ }
880
+ #reportSafely(event) {
881
+ try {
882
+ this.#reporter.report(event);
883
+ } catch {}
884
+ }
885
+ resolveEvaluationNow(seed) {
886
+ const candidate = seed.fields["now"];
887
+ if (candidate === void 0) return require_result.Result.ok(/* @__PURE__ */ new Date());
888
+ if (!require_temporal_guards.isValidDate(candidate)) return require_result.Result.err("seed.fields.now must be a valid Date when provided");
889
+ return require_result.Result.ok(candidate);
890
+ }
891
+ /**
892
+ * Evaluates a policy and returns its decision.
893
+ *
894
+ * This is the single public entry point. It delegates the actual work to the
895
+ * private pipeline and wraps it with telemetry: a completed or failed event
896
+ * is reported either way, but reporting is guarded so it can never change the
897
+ * outcome. The returned {@link Result} is `ok` with a
898
+ * {@link PolicyEvaluationResult} on success, or `err` with a typed
899
+ * {@link PolicyEvaluationError} describing which stage rejected the input —
900
+ * the method itself does not throw for expected failures.
901
+ *
902
+ * @param input - The policy key, scope chain, context version, and seed to
903
+ * evaluate, plus the caller's `decisionId`.
904
+ * @returns A `Result` carrying the decision or the evaluation error.
905
+ */
906
+ async evaluate(input) {
907
+ const result = await this.#evaluate(input);
908
+ if (result.isErr()) {
909
+ const error = result.errorOrNull();
910
+ this.#reportSafely({
911
+ kind: "policy-evaluation-failed",
912
+ level: "error",
913
+ policyKey: "policyKey" in error ? error.policyKey : void 0,
914
+ errorKind: error.kind,
915
+ message: error.message,
916
+ cause: error.cause,
917
+ occurredAt: /* @__PURE__ */ new Date()
918
+ });
919
+ } else {
920
+ const ok = result.getOrNull();
921
+ this.#reportSafely({
922
+ kind: "policy-evaluation-completed",
923
+ level: "info",
924
+ policyKey: ok.ref.policyKey,
925
+ decisionId: ok.decisionId,
926
+ engineKind: ok.kind,
927
+ occurredAt: ok.evaluatedAt
928
+ });
929
+ }
930
+ return result;
931
+ }
932
+ async #evaluate(input) {
933
+ const seedResult = ContextSeedValidator.validate(input.seed);
934
+ if (seedResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.invalidContext("SEED_VALIDATION", input.policyKey, seedResult.errorOrNull()));
935
+ const seed = seedResult.getOrNull();
936
+ const catalogFamilyResult = this.#resolveCatalogFamily(input.policyKey);
937
+ if (catalogFamilyResult.isErr()) return require_result.Result.err(catalogFamilyResult.errorOrNull());
938
+ const { key: policyKey, family } = catalogFamilyResult.getOrNull();
939
+ const familyEntry = family[0];
940
+ const nowResult = this.resolveEvaluationNow(seed);
941
+ if (nowResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.invalidContext("AS_OF_DERIVATION", policyKey, nowResult.errorOrNull()));
942
+ const now = nowResult.getOrNull();
943
+ const asOfResult = PolicyAsOfResolver.derive(familyEntry.asOfSource, seed, now, this.options.asOf);
944
+ if (asOfResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.invalidContext("AS_OF_DERIVATION", policyKey, asOfResult.errorOrNull()));
945
+ const asOf = asOfResult.getOrNull();
946
+ const definitionResult = this.#findCandidates(policyKey, familyEntry.kind, asOf, input);
947
+ if (definitionResult.isErr()) return require_result.Result.err(definitionResult.errorOrNull());
948
+ const { definition: policyDefinition, catalogEntry: versionedCatalogEntry } = definitionResult.getOrNull();
949
+ this.#reportSafely({
950
+ kind: "policy-resolution",
951
+ level: "info",
952
+ policyKey,
953
+ definitionId: policyDefinition.id,
954
+ policyVersion: policyDefinition.policyVersion,
955
+ engineKind: policyDefinition.isGate() ? "GATE" : "COMPUTE",
956
+ engineVersion: policyDefinition.isGate() ? policyDefinition.gateEngineVersion : policyDefinition.computeEngineVersion,
957
+ occurredAt: now
958
+ });
959
+ const ctxResult = await this.contextBuilder.build(versionedCatalogEntry.contextRequirements, seed);
960
+ if (ctxResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.invalidContext("CONTEXT_BUILD", policyKey, ctxResult.errorOrNull()));
961
+ const context = ctxResult.getOrNull();
962
+ const decisionResult = this.#executeEngine(policyKey, policyDefinition, context);
963
+ if (decisionResult.isErr()) return require_result.Result.err(decisionResult.errorOrNull());
964
+ const decision = decisionResult.getOrNull();
965
+ const result = {
966
+ decisionId: input.decisionId,
967
+ ref: {
968
+ definitionId: policyDefinition.id,
969
+ policyKey: policyDefinition.policyKey,
970
+ policyVersion: policyDefinition.policyVersion,
971
+ payloadHash: policyDefinition.payloadHash,
972
+ ...policyDefinition.isGate() ? { gateEngineVersion: policyDefinition.gateEngineVersion } : { computeEngineVersion: policyDefinition.computeEngineVersion }
973
+ },
974
+ kind: policyDefinition.kind,
975
+ asOf,
976
+ evaluatedAt: now,
977
+ contextVersion: input.contextVersion,
978
+ decision
979
+ };
980
+ return require_result.Result.ok(result);
981
+ }
982
+ #resolveCatalogVariant(definition) {
983
+ return this.catalog.getVersioned({
984
+ key: definition.policyKey,
985
+ kind: definition.kind,
986
+ ...definition.isGate() ? { gateEngineVersion: definition.gateEngineVersion } : { computeEngineVersion: definition.computeEngineVersion },
987
+ payloadSchemaVersion: definition.payloadSchemaVersion
988
+ });
989
+ }
990
+ #resolveCatalogFamily(rawKey) {
991
+ const keyResult = PolicyKey.parse(rawKey);
992
+ if (keyResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.invalidPolicyKey(rawKey, keyResult.errorOrNull()));
993
+ const policyKey = keyResult.getOrNull().toString();
994
+ const catalogFamilyResult = this.catalog.getFamily(policyKey);
995
+ if (catalogFamilyResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.policyNotFound(policyKey, catalogFamilyResult.errorOrNull()));
996
+ return require_result.Result.ok({
997
+ key: policyKey,
998
+ family: catalogFamilyResult.getOrNull()
999
+ });
1000
+ }
1001
+ #findCandidates(policyKey, policyKind, asOf, input) {
1002
+ const candidates = this.defRepo.findCandidates({
1003
+ policyKey,
1004
+ kind: policyKind,
1005
+ asOf,
1006
+ contextVersion: input.contextVersion,
1007
+ scopeChain: input.scopeChain
1008
+ });
1009
+ const compatibleCandidates = [];
1010
+ const compatibleCandidatesByDefinitionId = /* @__PURE__ */ new Map();
1011
+ const incompatibleVariantErrorsByDefinitionId = /* @__PURE__ */ new Map();
1012
+ for (const candidate of candidates) {
1013
+ const compatibilityResult = this.#resolveCatalogVariant(candidate);
1014
+ if (compatibilityResult.isErr()) {
1015
+ incompatibleVariantErrorsByDefinitionId.set(candidate.id, compatibilityResult.errorOrNull());
1016
+ continue;
1017
+ }
1018
+ const resolvedCandidate = {
1019
+ definition: candidate,
1020
+ catalogEntry: compatibilityResult.getOrNull()
1021
+ };
1022
+ compatibleCandidates.push(resolvedCandidate);
1023
+ compatibleCandidatesByDefinitionId.set(candidate.id, resolvedCandidate);
1024
+ }
1025
+ if (compatibleCandidates.length === 0 && candidates.length > 0) {
1026
+ const selectedCandidateResult = this.resolver.resolveBest(candidates);
1027
+ if (selectedCandidateResult.isOk()) {
1028
+ const selectedCandidate = selectedCandidateResult.getOrNull();
1029
+ return require_result.Result.err(PolicyEvaluationErrors.policyVariantNotFound({
1030
+ policyKey,
1031
+ policyKind: selectedCandidate.kind,
1032
+ payloadSchemaVersion: selectedCandidate.payloadSchemaVersion,
1033
+ ...selectedCandidate.isGate() ? { gateEngineVersion: selectedCandidate.gateEngineVersion } : { computeEngineVersion: selectedCandidate.computeEngineVersion },
1034
+ cause: incompatibleVariantErrorsByDefinitionId.get(selectedCandidate.id) ?? "No compatible catalog variant matched the selected policy definition"
1035
+ }));
1036
+ }
1037
+ }
1038
+ const bestResult = this.resolver.resolveBest(compatibleCandidates.map((candidate) => candidate.definition));
1039
+ if (bestResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.policyDefinitionNotFound(policyKey, asOf, input.contextVersion, bestResult.errorOrNull()));
1040
+ const bestDefinition = bestResult.getOrNull();
1041
+ const resolvedCandidate = compatibleCandidatesByDefinitionId.get(bestDefinition.id);
1042
+ if (resolvedCandidate === void 0) return require_result.Result.err(PolicyEvaluationErrors.policyDefinitionNotFound(policyKey, asOf, input.contextVersion, `Resolved definition "${bestDefinition.id}" is not present among compatible catalog variants`));
1043
+ return require_result.Result.ok(resolvedCandidate);
1044
+ }
1045
+ #executeEngine(policyKey, definition, context) {
1046
+ if (definition.isGate()) {
1047
+ const gateResult = this.gateEngines.evaluate(definition.gateEngineVersion, definition.payloadJson, context);
1048
+ if (gateResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.engineFailure(policyKey, "GATE", definition.gateEngineVersion, gateResult.errorOrNull()));
1049
+ return require_result.Result.ok(gateResult.getOrNull());
1050
+ }
1051
+ if (!definition.isCompute()) return require_result.Result.err(PolicyEvaluationErrors.policyVariantNotFound({
1052
+ policyKey,
1053
+ policyKind: definition.kind,
1054
+ payloadSchemaVersion: definition.payloadSchemaVersion,
1055
+ cause: `Unsupported policy kind "${definition.kind}"`
1056
+ }));
1057
+ const computeResult = this.computeRegistry.evaluate(policyKey, definition.computeEngineVersion, definition.payloadSchemaVersion, definition.payloadJson, context);
1058
+ if (computeResult.isErr()) return require_result.Result.err(PolicyEvaluationErrors.engineFailure(policyKey, "COMPUTE", definition.computeEngineVersion, computeResult.errorOrNull()));
1059
+ return require_result.Result.ok(computeResult.getOrNull());
1060
+ }
1061
+ };
1062
+ //#endregion
1063
+ Object.defineProperty(exports, "ContextResolverRegistry", {
1064
+ enumerable: true,
1065
+ get: function() {
1066
+ return ContextResolverRegistry;
1067
+ }
1068
+ });
1069
+ Object.defineProperty(exports, "ContextSeedValidator", {
1070
+ enumerable: true,
1071
+ get: function() {
1072
+ return ContextSeedValidator;
1073
+ }
1074
+ });
1075
+ Object.defineProperty(exports, "InMemoryPolicyDefinitionRepository", {
1076
+ enumerable: true,
1077
+ get: function() {
1078
+ return InMemoryPolicyDefinitionRepository;
1079
+ }
1080
+ });
1081
+ Object.defineProperty(exports, "PolicyAsOfResolver", {
1082
+ enumerable: true,
1083
+ get: function() {
1084
+ return PolicyAsOfResolver;
1085
+ }
1086
+ });
1087
+ Object.defineProperty(exports, "PolicyCatalog", {
1088
+ enumerable: true,
1089
+ get: function() {
1090
+ return PolicyCatalog;
1091
+ }
1092
+ });
1093
+ Object.defineProperty(exports, "PolicyCatalogFactory", {
1094
+ enumerable: true,
1095
+ get: function() {
1096
+ return PolicyCatalogFactory;
1097
+ }
1098
+ });
1099
+ Object.defineProperty(exports, "PolicyContextBuilder", {
1100
+ enumerable: true,
1101
+ get: function() {
1102
+ return PolicyContextBuilder;
1103
+ }
1104
+ });
1105
+ Object.defineProperty(exports, "PolicyDefinition", {
1106
+ enumerable: true,
1107
+ get: function() {
1108
+ return PolicyDefinition;
1109
+ }
1110
+ });
1111
+ Object.defineProperty(exports, "PolicyEvaluationErrors", {
1112
+ enumerable: true,
1113
+ get: function() {
1114
+ return PolicyEvaluationErrors;
1115
+ }
1116
+ });
1117
+ Object.defineProperty(exports, "PolicyHashing", {
1118
+ enumerable: true,
1119
+ get: function() {
1120
+ return PolicyHashing;
1121
+ }
1122
+ });
1123
+ Object.defineProperty(exports, "PolicyKey", {
1124
+ enumerable: true,
1125
+ get: function() {
1126
+ return PolicyKey;
1127
+ }
1128
+ });
1129
+ Object.defineProperty(exports, "PolicyResolver", {
1130
+ enumerable: true,
1131
+ get: function() {
1132
+ return PolicyResolver;
1133
+ }
1134
+ });
1135
+ Object.defineProperty(exports, "PolicyScopeMatcher", {
1136
+ enumerable: true,
1137
+ get: function() {
1138
+ return PolicyScopeMatcher;
1139
+ }
1140
+ });
1141
+ Object.defineProperty(exports, "PolicyService", {
1142
+ enumerable: true,
1143
+ get: function() {
1144
+ return PolicyService;
1145
+ }
1146
+ });
1147
+ Object.defineProperty(exports, "asPolicyDecisionId", {
1148
+ enumerable: true,
1149
+ get: function() {
1150
+ return asPolicyDecisionId;
1151
+ }
1152
+ });
1153
+ Object.defineProperty(exports, "asPolicyDefinitionId", {
1154
+ enumerable: true,
1155
+ get: function() {
1156
+ return asPolicyDefinitionId;
1157
+ }
1158
+ });
1159
+ Object.defineProperty(exports, "asSchoolId", {
1160
+ enumerable: true,
1161
+ get: function() {
1162
+ return asSchoolId;
1163
+ }
1164
+ });
1165
+ Object.defineProperty(exports, "asTenantId", {
1166
+ enumerable: true,
1167
+ get: function() {
1168
+ return asTenantId;
1169
+ }
1170
+ });
1171
+ Object.defineProperty(exports, "contextResolverRegistry", {
1172
+ enumerable: true,
1173
+ get: function() {
1174
+ return contextResolverRegistry;
1175
+ }
1176
+ });
1177
+ Object.defineProperty(exports, "registerNamespacedContextResolvers", {
1178
+ enumerable: true,
1179
+ get: function() {
1180
+ return registerNamespacedContextResolvers;
1181
+ }
1182
+ });
1183
+ Object.defineProperty(exports, "registerNamespacedContextResolversIn", {
1184
+ enumerable: true,
1185
+ get: function() {
1186
+ return registerNamespacedContextResolversIn;
1187
+ }
1188
+ });
1189
+
1190
+ //# sourceMappingURL=policy-service.cjs.map