@cullet/erp-core 1.0.10 → 1.1.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 (62) hide show
  1. package/KIT_CONTEXT.md +67 -0
  2. package/README.md +128 -1
  3. package/dist/app-error.d.ts +108 -0
  4. package/dist/application/index.d.ts +2 -0
  5. package/dist/application/index.js +2 -0
  6. package/dist/errors/index.d.ts +2 -1
  7. package/dist/errors/index.js +4 -2
  8. package/dist/gate-engine-registry.d.ts +80 -0
  9. package/dist/gate-v1-payload.schema.js +2 -28
  10. package/dist/gate-v1-payload.schema.js.map +1 -1
  11. package/dist/hashing.js +49 -0
  12. package/dist/hashing.js.map +1 -0
  13. package/dist/index.d.ts +122 -45
  14. package/dist/index.js +104 -85
  15. package/dist/index.js.map +1 -1
  16. package/dist/not-found-error.js +54 -0
  17. package/dist/not-found-error.js.map +1 -0
  18. package/dist/outcome.js +1 -1
  19. package/dist/parse-gate-payload.d.ts +3 -78
  20. package/dist/path.d.ts +89 -0
  21. package/dist/policies/engines/index.d.ts +2 -1
  22. package/dist/policies/index.d.ts +4 -2
  23. package/dist/policy-service.d.ts +102 -86
  24. package/dist/policy-service.js +78 -16
  25. package/dist/policy-service.js.map +1 -1
  26. package/dist/temporal-guards.js +30 -0
  27. package/dist/temporal-guards.js.map +1 -0
  28. package/dist/temporal-use-case.d.ts +309 -0
  29. package/dist/temporal-use-case.js +284 -0
  30. package/dist/temporal-use-case.js.map +1 -0
  31. package/dist/validation-code.js +34 -47
  32. package/dist/validation-code.js.map +1 -1
  33. package/dist/validation-error.d.ts +171 -74
  34. package/dist/validation-error.js +152 -38
  35. package/dist/validation-error.js.map +1 -1
  36. package/dist/validation-exception.js +18 -0
  37. package/dist/validation-exception.js.map +1 -0
  38. package/meta.json +4 -2
  39. package/package.json +5 -1
  40. package/src/application/index.ts +1 -0
  41. package/src/core/application/commands/index.ts +1 -0
  42. package/src/core/application/index.ts +12 -3
  43. package/src/core/application/ports/index.ts +2 -2
  44. package/src/core/application/ports/policy-port.ts +13 -3
  45. package/src/core/application/ports/repository.port.ts +37 -1
  46. package/src/core/application/temporal/temporal-use-case.ts +14 -2
  47. package/src/core/application/use-case.ts +133 -2
  48. package/src/core/domain/entity.ts +71 -0
  49. package/src/core/domain/value-object.ts +41 -0
  50. package/src/core/errors/app-error.ts +33 -0
  51. package/src/core/errors/authorization-error.ts +43 -0
  52. package/src/core/errors/conflict-error.ts +69 -0
  53. package/src/core/errors/integration-error.ts +25 -0
  54. package/src/core/errors/not-found-error.ts +14 -0
  55. package/src/core/errors/validation-error.ts +18 -0
  56. package/src/core/index.ts +1 -10
  57. package/src/core/policies/catalog/policy-catalog.ts +36 -0
  58. package/src/core/policies/resolver/policy-resolver.ts +10 -0
  59. package/src/core/policies/service/policy-service.ts +53 -0
  60. package/src/examples/application/cancel-order.example.ts +124 -0
  61. package/src/examples/application/in-memory-account-repository.example.ts +73 -0
  62. package/src/version.ts +1 -1
@@ -1,36 +1,6 @@
1
- import { C as TenantId, I as Result, S as SchoolId, b as PolicyDecisionId, g as CoreConfig, t as GateOutcome, u as GatePayload, x as PolicyDefinitionId, y as PolicyReporter } from "./gate-types.js";
2
- import { _ as ComputePayload, c as ComputeRegistry, f as ComputeEvaluatorRegistration, p as ComputeOutcome, s as GateEngineRegistry } from "./parse-gate-payload.js";
1
+ import { C as TenantId, I as Result, S as SchoolId, b as PolicyDecisionId, t as GateOutcome, u as GatePayload, x as PolicyDefinitionId, y as PolicyReporter } from "./gate-types.js";
2
+ import { a as ComputeOutcome, l as ComputePayload, n as ComputeRegistry, t as GateEngineRegistry } from "./gate-engine-registry.js";
3
3
 
4
- //#region src/core/config/core-config.instance.d.ts
5
-
6
- /**
7
- * Shared instance for the application composition root.
8
- * Use `new CoreConfig()` for isolated runtimes, tests, or multi-tenant hosts.
9
- *
10
- * The default reporter is silent so the core has no runtime logging dependency.
11
- * Hosts can swap in their own `PolicyReporter` via
12
- * `coreConfig.configure({ observability: { reporter } })`.
13
- */
14
- declare const coreConfig: CoreConfig;
15
- //#endregion
16
- //#region src/core/policies/utils/hash.d.ts
17
- /**
18
- * Produces a deterministic SHA-256 hex digest of a canonical JSON representation.
19
- * Object keys are sorted recursively so key order does not affect the hash.
20
- * Array item order is preserved on purpose; callers that treat arrays as sets
21
- * must normalize or sort them before hashing.
22
- *
23
- * Values that JSON.stringify would silently drop or coerce (undefined,
24
- * non-finite numbers, functions, symbols, bigint) are rejected up-front to
25
- * prevent semantically different payloads from collapsing to the same hash.
26
- */
27
- declare class PolicyHashing {
28
- static sha256(input: string): string;
29
- private static assertHashable;
30
- static canonicalJson(value: unknown): string;
31
- static computePayloadHash(payload: unknown, policyKey: string, policyVersion: string): string;
32
- }
33
- //#endregion
34
4
  //#region src/core/policies/catalog/catalog-types.d.ts
35
5
  type PolicyKind = "GATE" | "COMPUTE";
36
6
  type PolicyOwner = "PLATFORM_OWNER" | "TENANT_ADMIN" | "SCHOOL_ADMIN";
@@ -116,9 +86,43 @@ declare class PolicyCatalogEntry {
116
86
  declare class PolicyCatalog {
117
87
  private readonly versionedEntries;
118
88
  private readonly entriesByKey;
89
+ /**
90
+ * Indexes the given entries up front into two lookups — one per exact
91
+ * variant, one per policy key (the "family") — and validates them eagerly:
92
+ * a duplicate variant or an internally inconsistent family throws here, at
93
+ * wiring time, so a malformed catalog fails fast at startup rather than on
94
+ * the first evaluation.
95
+ *
96
+ * @throws {UnexpectedError} When two entries resolve to the same variant key.
97
+ */
119
98
  constructor(entries: readonly PolicyCatalogEntry[]);
99
+ /**
100
+ * Returns the single entry for a key. Deliberately strict: if the key has
101
+ * more than one variant it errs rather than guessing, steering the caller to
102
+ * {@link getVersioned} or {@link getFamily}. Use this only when a key is
103
+ * known to be unambiguous.
104
+ *
105
+ * @returns `ok` with the entry, or `err` when the key is unknown or ambiguous.
106
+ */
120
107
  get(key: string): Result<PolicyCatalogEntry, string>;
108
+ /**
109
+ * Returns every variant registered under a key — the whole "family". This is
110
+ * the lookup the evaluation pipeline uses, since a single key may host
111
+ * several engine/schema variants that the resolver then chooses between.
112
+ *
113
+ * @returns `ok` with the family (always non-empty), or `err` when the key is unknown.
114
+ */
121
115
  getFamily(key: string): Result<readonly PolicyCatalogEntry[], string>;
116
+ /**
117
+ * Resolves the one entry matching an exact variant — kind plus engine and
118
+ * payload-schema versions. Falls back to the sole family member when a key
119
+ * has exactly one entry and that entry declares no explicit version
120
+ * selector, so unversioned single-variant policies "just work" without the
121
+ * caller spelling out versions.
122
+ *
123
+ * @param params - The key and the variant coordinates to match on.
124
+ * @returns `ok` with the matching entry, or `err` when no variant matches.
125
+ */
122
126
  getVersioned(params: {
123
127
  readonly key: string;
124
128
  readonly kind: "GATE" | "COMPUTE";
@@ -126,7 +130,9 @@ declare class PolicyCatalog {
126
130
  readonly computeEngineVersion?: number;
127
131
  readonly payloadSchemaVersion?: number;
128
132
  }): Result<PolicyCatalogEntry, string>;
133
+ /** Lists every registered variant across all keys — useful for introspection and diagnostics. */
129
134
  list(): readonly PolicyCatalogEntry[];
135
+ /** Returns whether any variant is registered under the given key. */
130
136
  has(key: string): boolean;
131
137
  }
132
138
  //#endregion
@@ -253,39 +259,6 @@ interface PolicyDefinitionRepository {
253
259
  findCandidates(params: FindCandidatesParams): PolicyDefinition[];
254
260
  }
255
261
  //#endregion
256
- //#region src/core/policies/defs/in-memory-policy-definition-repo.d.ts
257
- declare class InMemoryPolicyDefinitionRepository implements PolicyDefinitionRepository {
258
- private readonly definitions;
259
- private readonly definitionsByPolicyKey;
260
- constructor(definitions: readonly PolicyDefinition[]);
261
- findCandidates(params: FindCandidatesParams): PolicyDefinition[];
262
- }
263
- //#endregion
264
- //#region src/core/policies/package/policy-package.d.ts
265
- /**
266
- * Protocol of contribution: a module declares what it wants to add to the
267
- * policy system without the core knowing which module it is.
268
- *
269
- * - catalogEntries: static policy descriptors the catalog will register.
270
- * - definitions: default policy definitions (rules/parameters) to seed.
271
- * - evaluators: compute evaluator registrations keyed by policy/version.
272
- */
273
- interface PolicyPackage {
274
- readonly catalogEntries: readonly PolicyCatalogEntryProps[];
275
- readonly definitions: readonly PolicyDefinition[];
276
- readonly evaluators: readonly ComputeEvaluatorRegistration[];
277
- }
278
- //#endregion
279
- //#region src/core/policies/catalog/catalog.instance.d.ts
280
- /**
281
- * Aggregates catalog entries contributed by each module package into a single
282
- * PolicyCatalog. The core knows nothing about concrete modules — callers
283
- * are responsible for passing all relevant packages at composition time.
284
- */
285
- declare class PolicyCatalogFactory {
286
- static build(packages: readonly PolicyPackage[]): PolicyCatalog;
287
- }
288
- //#endregion
289
262
  //#region src/core/policies/context/context-seed.d.ts
290
263
  /**
291
264
  * Seed data provided by the use case / caller.
@@ -381,26 +354,6 @@ declare class PolicyContextBuilder {
381
354
  build(requirements: readonly string[], seed: ContextSeed): Promise<Result<Record<string, unknown>, string>>;
382
355
  }
383
356
  //#endregion
384
- //#region src/core/policies/context/context-registry.instance.d.ts
385
- /**
386
- * Official instance of the ContextResolverRegistry.
387
- * Use `new ContextResolverRegistry()` plus `registerNamespacedContextResolversIn`
388
- * when each runtime/composition root needs its own isolated resolver graph.
389
- */
390
- declare const contextResolverRegistry: ContextResolverRegistry;
391
- declare function registerNamespacedContextResolvers(namespace: string, resolvers: readonly ContextValueResolver[]): Result<ContextResolverRegistry, string>;
392
- //#endregion
393
- //#region src/core/policies/context/path.d.ts
394
- declare class PolicyContextPath {
395
- private static readonly FORBIDDEN_SEGMENTS;
396
- private static resolve;
397
- private static parseSegments;
398
- private static isPlainRecord;
399
- static getOrAbsent(obj: Record<string, unknown>, path: string): Result<unknown, "absent">;
400
- static has(obj: Record<string, unknown>, path: string): boolean;
401
- static set(obj: Record<string, unknown>, path: string, value: unknown): Result<void, string>;
402
- }
403
- //#endregion
404
357
  //#region src/core/policies/asof/asof.d.ts
405
358
  interface DeriveAsOfOptions {
406
359
  readonly maxFutureYears?: number;
@@ -422,6 +375,16 @@ declare class PolicyAsOfResolver {
422
375
  * 5. policyVersion descending (semver — final deterministic tiebreaker)
423
376
  */
424
377
  declare class PolicyResolver {
378
+ /**
379
+ * Picks the single winning definition from an already-filtered candidate
380
+ * list by applying the class's ordering criteria in priority order. The sort
381
+ * runs over a copy, so the caller's array is left untouched, and the final
382
+ * semver tiebreaker guarantees the winner is fully deterministic — it never
383
+ * depends on the order the repository returned the candidates in.
384
+ *
385
+ * @param candidates - Definitions already filtered to match key, scope, and as-of.
386
+ * @returns `ok` with the highest-ranked definition, or `err` when the list is empty.
387
+ */
425
388
  resolveBest(candidates: readonly PolicyDefinition[]): Result<PolicyDefinition, string>;
426
389
  }
427
390
  //#endregion
@@ -485,6 +448,13 @@ declare class PolicyEvaluationErrors {
485
448
  }
486
449
  //#endregion
487
450
  //#region src/core/policies/service/policy-service.d.ts
451
+ /**
452
+ * Everything a single policy evaluation needs. `policyKey` names the policy,
453
+ * `scopeChain` narrows the search from most-specific to least-specific scope,
454
+ * `contextVersion` pins which context schema applies, and `seed` carries the
455
+ * raw facts the context builder expands. `decisionId` is the caller's
456
+ * idempotency/audit handle, echoed back on the result.
457
+ */
488
458
  interface EvaluateInput {
489
459
  readonly decisionId: PolicyDecisionId;
490
460
  readonly policyKey: string;
@@ -492,10 +462,22 @@ interface EvaluateInput {
492
462
  readonly contextVersion: number;
493
463
  readonly seed: ContextSeed;
494
464
  }
465
+ /**
466
+ * Cross-cutting knobs for a {@link PolicyService}: how the as-of instant is
467
+ * derived ({@link asOf}) and where evaluation telemetry is sent
468
+ * ({@link reporter}). Both are optional; the service defaults to a silent
469
+ * reporter so telemetry is opt-in.
470
+ */
495
471
  interface PolicyServiceOptions {
496
472
  readonly asOf?: DeriveAsOfOptions;
497
473
  readonly reporter?: PolicyReporter;
498
474
  }
475
+ /**
476
+ * The collaborators a {@link PolicyService} is wired with. Each is an injected
477
+ * port so the service stays storage- and engine-agnostic: the catalog defines
478
+ * the universe of policies, `defRepo` supplies their versioned definitions, the
479
+ * `resolver` picks the winning definition, and the engine registries execute it.
480
+ */
499
481
  interface PolicyServiceParams {
500
482
  readonly catalog: PolicyCatalog;
501
483
  readonly contextBuilder: PolicyContextBuilder;
@@ -507,6 +489,13 @@ interface PolicyServiceParams {
507
489
  }
508
490
  /** Union of all possible policy decision Outcomes. */
509
491
  type PolicyDecision = GateOutcome | ComputeOutcome;
492
+ /**
493
+ * The full record of one successful evaluation. Beyond the business
494
+ * {@link decision}, it pins the exact definition that produced it — id, key,
495
+ * version, payload hash, and engine version — together with the `asOf` instant
496
+ * the policy was selected for and the wall-clock `evaluatedAt`. That provenance
497
+ * is what makes a decision reproducible and auditable after the fact.
498
+ */
510
499
  interface PolicyEvaluationResult {
511
500
  readonly decisionId: PolicyDecisionId;
512
501
  readonly ref: {
@@ -524,6 +513,18 @@ interface PolicyEvaluationResult {
524
513
  /** Business decision expressed as an Outcome — not a technical Result. */
525
514
  readonly decision: PolicyDecision;
526
515
  }
516
+ /**
517
+ * Orchestrates the end-to-end evaluation of a policy: it parses and validates
518
+ * the context seed, derives the as-of instant, resolves the best matching
519
+ * definition for the requested key/scope/version, builds the context the policy
520
+ * needs, and runs it through the right engine (gate or compute).
521
+ *
522
+ * The service never throws for an expected failure: every step returns a
523
+ * {@link Result}, and the failures are folded into a typed
524
+ * {@link PolicyEvaluationError} so callers branch on `isErr()` rather than
525
+ * catching. Telemetry is best-effort — a throwing reporter can never abort an
526
+ * evaluation — which keeps observability strictly side-band.
527
+ */
527
528
  declare class PolicyService {
528
529
  #private;
529
530
  private readonly catalog;
@@ -535,8 +536,23 @@ declare class PolicyService {
535
536
  private readonly options;
536
537
  constructor(params: PolicyServiceParams);
537
538
  private resolveEvaluationNow;
539
+ /**
540
+ * Evaluates a policy and returns its decision.
541
+ *
542
+ * This is the single public entry point. It delegates the actual work to the
543
+ * private pipeline and wraps it with telemetry: a completed or failed event
544
+ * is reported either way, but reporting is guarded so it can never change the
545
+ * outcome. The returned {@link Result} is `ok` with a
546
+ * {@link PolicyEvaluationResult} on success, or `err` with a typed
547
+ * {@link PolicyEvaluationError} describing which stage rejected the input —
548
+ * the method itself does not throw for expected failures.
549
+ *
550
+ * @param input - The policy key, scope chain, context version, and seed to
551
+ * evaluate, plus the caller's `decisionId`.
552
+ * @returns A `Result` carrying the decision or the evaluation error.
553
+ */
538
554
  evaluate(input: EvaluateInput): Promise<Result<PolicyEvaluationResult, PolicyEvaluationError>>;
539
555
  }
540
556
  //#endregion
541
- export { FindCandidatesParams as A, PolicyKey as B, ContextSeed as C, InMemoryPolicyDefinitionRepository as D, PolicyPackage as E, PolicyScope as F, PolicyHashing as G, PolicyKind as H, PolicyScopeMatcher as I, coreConfig as K, ScopeChain as L, PolicyDefinition as M, PolicyDefinitionProps as N, BasePolicyDefinitionProps as O, PolicyDefinitionStatus as P, PolicyCatalog as R, ContextValueResolver as S, PolicyCatalogFactory as T, PolicyOwner as U, AsOfSource as V, PolicyScopeLevel as W, ContextResolverRegistry as _, PolicyServiceOptions as a, ContextResolverResilienceOptions as b, PolicyEvaluationErrors as c, PolicyAsOfResolver as d, PolicyContextPath as f, PolicyContextBuilderOptions as g, PolicyContextBuilder as h, PolicyService as i, GatePolicyDefinitionProps as j, ComputePolicyDefinitionProps as k, PolicyResolver as l, registerNamespacedContextResolvers as m, PolicyDecision as n, PolicyServiceParams as o, contextResolverRegistry as p, PolicyEvaluationResult as r, PolicyEvaluationError as s, EvaluateInput as t, DeriveAsOfOptions as u, registerNamespacedContextResolversIn as v, ContextSeedValidator as w, ContextResolverRetryOptions as x, ContextResolverCircuitBreakerOptions as y, PolicyCatalogEntryProps as z };
557
+ export { PolicyScope as A, BasePolicyDefinitionProps as C, PolicyDefinition as D, GatePolicyDefinitionProps as E, PolicyKey as F, AsOfSource as I, PolicyKind as L, ScopeChain as M, PolicyCatalog as N, PolicyDefinitionProps as O, PolicyCatalogEntryProps as P, PolicyOwner as R, PolicyDefinitionRepository as S, FindCandidatesParams as T, ContextResolverResilienceOptions as _, PolicyServiceOptions as a, ContextSeed as b, PolicyEvaluationErrors as c, PolicyAsOfResolver as d, PolicyContextBuilder as f, ContextResolverCircuitBreakerOptions as g, registerNamespacedContextResolversIn as h, PolicyService as i, PolicyScopeMatcher as j, PolicyDefinitionStatus as k, PolicyResolver as l, ContextResolverRegistry as m, PolicyDecision as n, PolicyServiceParams as o, PolicyContextBuilderOptions as p, PolicyEvaluationResult as r, PolicyEvaluationError as s, EvaluateInput as t, DeriveAsOfOptions as u, ContextResolverRetryOptions as v, ComputePolicyDefinitionProps as w, ContextSeedValidator as x, ContextValueResolver as y, PolicyScopeLevel as z };
542
558
  //# sourceMappingURL=policy-service.d.ts.map
@@ -1,6 +1,9 @@
1
- import { a as stableStringify, i as sha256Hex, n as UnexpectedError, t as ValidationCode } from "./validation-code.js";
1
+ import { n as UnexpectedError, t as ValidationCode } from "./validation-code.js";
2
+ import { n as sha256Hex, r as stableStringify } from "./hashing.js";
2
3
  import { t as ValidationField } from "./validation-field.js";
3
- import { _ as DomainException, f as SilentPolicyReporter, g as InvariantViolationException, h as isValidDate, l as Result, o as PolicyContextPath } from "./gate-v1-payload.schema.js";
4
+ import { i as InvariantViolationException, r as isValidDate } from "./temporal-guards.js";
5
+ import { t as InvalidValueException } from "./validation-exception.js";
6
+ import { f as SilentPolicyReporter, l as Result, o as PolicyContextPath } from "./gate-v1-payload.schema.js";
4
7
  //#region src/core/policies/utils/hash.ts
5
8
  /**
6
9
  * Produces a deterministic SHA-256 hex digest of a canonical JSON representation.
@@ -49,20 +52,6 @@ var PolicyHashing = class PolicyHashing {
49
52
  }
50
53
  };
51
54
  //#endregion
52
- //#region src/core/exceptions/validation-exception.ts
53
- var ValidationException = class extends DomainException {
54
- constructor(field, code, message) {
55
- super(message);
56
- this.field = field;
57
- this.code = code;
58
- }
59
- };
60
- var InvalidValueException = class extends ValidationException {
61
- constructor(field, code, message) {
62
- super(field, code, message);
63
- }
64
- };
65
- //#endregion
66
55
  //#region src/core/policies/policy-ids.ts
67
56
  const POLICY_DEFINITION_ID_FIELD = ValidationField.of("policyDefinitionId");
68
57
  const POLICY_DECISION_ID_FIELD = ValidationField.of("policyDecisionId");
@@ -191,6 +180,15 @@ var PolicyCatalogEntry = class PolicyCatalogEntry {
191
180
  * The catalog defines the universe of policies; the database cannot invent new ones.
192
181
  */
193
182
  var PolicyCatalog = class {
183
+ /**
184
+ * Indexes the given entries up front into two lookups — one per exact
185
+ * variant, one per policy key (the "family") — and validates them eagerly:
186
+ * a duplicate variant or an internally inconsistent family throws here, at
187
+ * wiring time, so a malformed catalog fails fast at startup rather than on
188
+ * the first evaluation.
189
+ *
190
+ * @throws {UnexpectedError} When two entries resolve to the same variant key.
191
+ */
194
192
  constructor(entries) {
195
193
  const families = /* @__PURE__ */ new Map();
196
194
  const versionedMap = /* @__PURE__ */ new Map();
@@ -208,6 +206,14 @@ var PolicyCatalog = class {
208
206
  this.versionedEntries = versionedMap;
209
207
  this.entriesByKey = families;
210
208
  }
209
+ /**
210
+ * Returns the single entry for a key. Deliberately strict: if the key has
211
+ * more than one variant it errs rather than guessing, steering the caller to
212
+ * {@link getVersioned} or {@link getFamily}. Use this only when a key is
213
+ * known to be unambiguous.
214
+ *
215
+ * @returns `ok` with the entry, or `err` when the key is unknown or ambiguous.
216
+ */
211
217
  get(key) {
212
218
  const familyResult = this.getFamily(key);
213
219
  if (familyResult.isErr()) return Result.err(familyResult.errorOrNull());
@@ -217,11 +223,28 @@ var PolicyCatalog = class {
217
223
  if (!entry) return Result.err(`Policy not found in catalog: "${key}"`);
218
224
  return Result.ok(entry);
219
225
  }
226
+ /**
227
+ * Returns every variant registered under a key — the whole "family". This is
228
+ * the lookup the evaluation pipeline uses, since a single key may host
229
+ * several engine/schema variants that the resolver then chooses between.
230
+ *
231
+ * @returns `ok` with the family (always non-empty), or `err` when the key is unknown.
232
+ */
220
233
  getFamily(key) {
221
234
  const family = this.entriesByKey.get(key);
222
235
  if (!family) return Result.err(`Policy not found in catalog: "${key}"`);
223
236
  return Result.ok(family);
224
237
  }
238
+ /**
239
+ * Resolves the one entry matching an exact variant — kind plus engine and
240
+ * payload-schema versions. Falls back to the sole family member when a key
241
+ * has exactly one entry and that entry declares no explicit version
242
+ * selector, so unversioned single-variant policies "just work" without the
243
+ * caller spelling out versions.
244
+ *
245
+ * @param params - The key and the variant coordinates to match on.
246
+ * @returns `ok` with the matching entry, or `err` when no variant matches.
247
+ */
225
248
  getVersioned(params) {
226
249
  const family = this.entriesByKey.get(params.key);
227
250
  if (!family || family.length === 0) return Result.err(`Policy not found in catalog: "${params.key}"`);
@@ -236,9 +259,11 @@ var PolicyCatalog = class {
236
259
  ].filter((part) => part !== null).join(", ");
237
260
  return Result.err(`Policy variant not found in catalog: "${params.key}" (${versionDetails})`);
238
261
  }
262
+ /** Lists every registered variant across all keys — useful for introspection and diagnostics. */
239
263
  list() {
240
264
  return Array.from(this.versionedEntries.values());
241
265
  }
266
+ /** Returns whether any variant is registered under the given key. */
242
267
  has(key) {
243
268
  return this.entriesByKey.has(key);
244
269
  }
@@ -734,6 +759,16 @@ var PolicyAsOfResolver = class PolicyAsOfResolver {
734
759
  * 5. policyVersion descending (semver — final deterministic tiebreaker)
735
760
  */
736
761
  var PolicyResolver = class {
762
+ /**
763
+ * Picks the single winning definition from an already-filtered candidate
764
+ * list by applying the class's ordering criteria in priority order. The sort
765
+ * runs over a copy, so the caller's array is left untouched, and the final
766
+ * semver tiebreaker guarantees the winner is fully deterministic — it never
767
+ * depends on the order the repository returned the candidates in.
768
+ *
769
+ * @param candidates - Definitions already filtered to match key, scope, and as-of.
770
+ * @returns `ok` with the highest-ranked definition, or `err` when the list is empty.
771
+ */
737
772
  resolveBest(candidates) {
738
773
  if (candidates.length === 0) return Result.err("No matching policy definition found (candidates list is empty)");
739
774
  const sorted = [...candidates].sort((a, b) => {
@@ -815,6 +850,18 @@ var PolicyEvaluationErrors = class {
815
850
  };
816
851
  //#endregion
817
852
  //#region src/core/policies/service/policy-service.ts
853
+ /**
854
+ * Orchestrates the end-to-end evaluation of a policy: it parses and validates
855
+ * the context seed, derives the as-of instant, resolves the best matching
856
+ * definition for the requested key/scope/version, builds the context the policy
857
+ * needs, and runs it through the right engine (gate or compute).
858
+ *
859
+ * The service never throws for an expected failure: every step returns a
860
+ * {@link Result}, and the failures are folded into a typed
861
+ * {@link PolicyEvaluationError} so callers branch on `isErr()` rather than
862
+ * catching. Telemetry is best-effort — a throwing reporter can never abort an
863
+ * evaluation — which keeps observability strictly side-band.
864
+ */
818
865
  var PolicyService = class {
819
866
  #reporter;
820
867
  constructor(params) {
@@ -838,6 +885,21 @@ var PolicyService = class {
838
885
  if (!isValidDate(candidate)) return Result.err("seed.fields.now must be a valid Date when provided");
839
886
  return Result.ok(candidate);
840
887
  }
888
+ /**
889
+ * Evaluates a policy and returns its decision.
890
+ *
891
+ * This is the single public entry point. It delegates the actual work to the
892
+ * private pipeline and wraps it with telemetry: a completed or failed event
893
+ * is reported either way, but reporting is guarded so it can never change the
894
+ * outcome. The returned {@link Result} is `ok` with a
895
+ * {@link PolicyEvaluationResult} on success, or `err` with a typed
896
+ * {@link PolicyEvaluationError} describing which stage rejected the input —
897
+ * the method itself does not throw for expected failures.
898
+ *
899
+ * @param input - The policy key, scope chain, context version, and seed to
900
+ * evaluate, plus the caller's `decisionId`.
901
+ * @returns A `Result` carrying the decision or the evaluation error.
902
+ */
841
903
  async evaluate(input) {
842
904
  const result = await this.#evaluate(input);
843
905
  if (result.isErr()) {