@orkestrel/qualifier 0.0.13 → 0.0.14

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.
@@ -1,22 +1,22 @@
1
- import { Check } from '@orkestrel/reason';
2
- import { CheckResult } from '@orkestrel/reason';
3
- import { Comparison } from '@orkestrel/reason';
4
- import { EmitterErrorHandler } from '@orkestrel/emitter';
5
- import { EmitterHooks } from '@orkestrel/emitter';
6
- import { EmitterInterface } from '@orkestrel/emitter';
7
- import { EvaluatorInterface } from '@orkestrel/reason';
8
- import { FieldPath } from '@orkestrel/contract';
1
+ import type { Check } from '@orkestrel/reason';
2
+ import type { CheckResult } from '@orkestrel/reason';
3
+ import type { Comparison } from '@orkestrel/reason';
4
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
5
+ import type { EmitterHooks } from '@orkestrel/emitter';
6
+ import type { EmitterInterface } from '@orkestrel/emitter';
7
+ import type { EvaluatorInterface } from '@orkestrel/reason';
8
+ import type { FieldPath } from '@orkestrel/contract';
9
9
  import { Guard } from '@orkestrel/contract';
10
- import { JSONValue } from '@orkestrel/contract';
11
- import { LogicalDefinition } from '@orkestrel/reason';
12
- import { LogicalResult } from '@orkestrel/reason';
13
- import { QuantitativeDefinition } from '@orkestrel/reason';
14
- import { QuantitativeResult } from '@orkestrel/reason';
15
- import { ReasonInterface } from '@orkestrel/reason';
16
- import { ReasonResult } from '@orkestrel/reason';
17
- import { ReasonValidationResult } from '@orkestrel/reason';
18
- import { Rule } from '@orkestrel/reason';
19
- import { Subject } from '@orkestrel/reason';
10
+ import type { JSONValue } from '@orkestrel/contract';
11
+ import type { LogicalDefinition } from '@orkestrel/reason';
12
+ import type { LogicalResult } from '@orkestrel/reason';
13
+ import type { QuantitativeDefinition } from '@orkestrel/reason';
14
+ import type { QuantitativeResult } from '@orkestrel/reason';
15
+ import type { ReasonInterface } from '@orkestrel/reason';
16
+ import type { ReasonResult } from '@orkestrel/reason';
17
+ import type { ReasonValidationResult } from '@orkestrel/reason';
18
+ import type { Rule } from '@orkestrel/reason';
19
+ import type { Subject } from '@orkestrel/reason';
20
20
 
21
21
  /**
22
22
  * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.
@@ -71,10 +71,9 @@ import { Subject } from '@orkestrel/reason';
71
71
  export declare function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility;
72
72
 
73
73
  /**
74
- * Creates a {@link QualificationDefinition}.
74
+ * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.
75
75
  *
76
76
  * @remarks
77
- * Returns a fresh top-level definition, omitting absent optional keys;
78
77
  * `passes` and `rulings` arrays are copied, and record `metadata` is
79
78
  * shallow-copied so nested values are not deep-cloned.
80
79
  *
@@ -94,10 +93,10 @@ import { Subject } from '@orkestrel/reason';
94
93
  export declare function createQualificationDefinition(id: string, name: string, passes: readonly QualificationPass[], input?: QualificationInput): QualificationDefinition;
95
94
 
96
95
  /**
97
- * Creates one qualifier over a reason engine.
96
+ * Creates one {@link QualifierInterface} over a reason engine.
98
97
  *
99
98
  * @remarks
100
- * A standalone qualifier creates and OWNS one shared quantitative-plus-logical
99
+ * A standalone qualifier creates and owns one shared quantitative-plus-logical
101
100
  * reason engine, destroying it on `destroy()`. An injected `options.engine`
102
101
  * remains caller-owned and is never destroyed. `validate` runs semantic
103
102
  * validation before every qualification. Default: `true`. `labels` overrides the
@@ -107,19 +106,44 @@ import { Subject } from '@orkestrel/reason';
107
106
  * @param options - Optional injected engine, validation, labels, and emitter hooks
108
107
  * @returns A {@link QualifierInterface}
109
108
  *
110
- * @example
109
+ * @example Create a qualifier
111
110
  * ```ts
112
- * import { createQualifier } from '@orkestrel/qualifier'
111
+ * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'
112
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
113
+ *
114
+ * const gates = createLogicalDefinition('gates', 'Eligibility gates', [
115
+ * createRule(
116
+ * 'licensed',
117
+ * [createAtom('licensed', 'equals', false)],
118
+ * createAtom('blocked', 'equals', true),
119
+ * ),
120
+ * ])
121
+ *
122
+ * const bare = createRuling('license', 'gates', 'licensed', 'restriction')
123
+ * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {
124
+ * message: 'A license is required',
125
+ * })
126
+ *
127
+ * 'message' in bare // false — an absent optional key is omitted, never written as undefined
128
+ * messaged.message // 'A license is required'
129
+ *
130
+ * const passes = [gates]
131
+ * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {
132
+ * rulings: [messaged],
133
+ * })
134
+ *
135
+ * 'description' in definition // false
136
+ * definition.passes === passes // false — the factory copies what it is handed
113
137
  *
114
138
  * const qualifier = createQualifier()
115
- * qualifier.qualify({ id: 'risk-1' }, definition)
139
+ * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)
116
140
  * qualifier.destroy()
117
141
  * ```
118
142
  */
119
143
  export declare function createQualifier(options?: QualifierOptions): QualifierInterface;
120
144
 
121
145
  /**
122
- * Creates a {@link Ruling} — one authored consequence for one rule in one pass.
146
+ * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.
123
147
  *
124
148
  * @param id - The ruling id
125
149
  * @param pass - The logical pass id the rule lives in
@@ -137,7 +161,10 @@ import { Subject } from '@orkestrel/reason';
137
161
  */
138
162
  export declare function createRuling(id: string, pass: string, rule: string, effect: QualificationEffect, input?: RulingInput): Ruling;
139
163
 
140
- /** Holds the default definition validation policy for `createQualifier` / `Qualifier.qualify`. */
164
+ /**
165
+ * Holds the default definition validation policy for `createQualifier` and
166
+ * `Qualifier.qualify`, `true`.
167
+ */
141
168
  export declare const DEFAULT_QUALIFIER_VALIDATE = true;
142
169
 
143
170
  /** Represents one quantitative pass's audit result. */
@@ -272,13 +299,16 @@ import { Subject } from '@orkestrel/reason';
272
299
  */
273
300
  export declare function describeUnreadDerivations(definition: QualificationDefinition): readonly string[];
274
301
 
275
- /** Maps each {@link QualificationEffect} to its eligibility impact; `condition` remains eligible. */
302
+ /**
303
+ * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to
304
+ * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.
305
+ */
276
306
  export declare const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>>;
277
307
 
278
308
  /** Represents the eligibility outcome axis. */
279
309
  export declare type Eligibility = 'eligible' | 'ineligible' | 'referral';
280
310
 
281
- /** Lists the eligibility severities in order — most to least severe. */
311
+ /** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */
282
312
  export declare const ELIGIBILITY_PRECEDENCE: readonly Eligibility[];
283
313
 
284
314
  /** Represents one resolved ruling. */
@@ -334,10 +364,10 @@ import { Subject } from '@orkestrel/reason';
334
364
  *
335
365
  * @remarks
336
366
  * Each token is split on `.` into a `FieldPath` array and resolved with
337
- * `resolveField` (a plain string field is ONE key, never dot-split — the split
367
+ * `resolveField` (a plain string field is one key, never dot-split — the split
338
368
  * here is the token-to-path bridge). A finite number renders with `en-US`
339
369
  * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.
340
- * An UNRESOLVED path renders as the empty string.
370
+ * An unresolved path renders as the empty string.
341
371
  *
342
372
  * @param template - The message template carrying `{{dotted.path}}` tokens
343
373
  * @param subject - The record tokens resolve against
@@ -357,8 +387,10 @@ import { Subject } from '@orkestrel/reason';
357
387
  * Determines whether a value is an open result-side {@link Derivation}.
358
388
  *
359
389
  * @remarks
360
- * Unknown members are admitted. `value` follows the published `number` type, including `NaN` and
361
- * infinities.
390
+ * Every published member is checked: `value` follows the published `number` type, including
391
+ * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are
392
+ * admitted, because a foreign result implementation may add them without changing the
393
+ * published contract.
362
394
  *
363
395
  * @param value - The value to test
364
396
  * @returns True if every published derivation member is valid; false otherwise
@@ -368,6 +400,10 @@ import { Subject } from '@orkestrel/reason';
368
400
  /**
369
401
  * Determines whether a value is an {@link Eligibility} literal.
370
402
  *
403
+ * @remarks
404
+ * The guard is total and leaves nothing unchecked: a scalar union has no open or exact
405
+ * axis.
406
+ *
371
407
  * @param value - The value to test
372
408
  * @returns True if `value` is one of the eligibility literals; false otherwise
373
409
  */
@@ -377,8 +413,9 @@ import { Subject } from '@orkestrel/reason';
377
413
  * Determines whether a value is an open string-keyed record of {@link Eligibility} values.
378
414
  *
379
415
  * @remarks
380
- * Every own string-named property is checked, including non-enumerable properties. Inherited and
381
- * symbol-named members are left unchecked because they are outside the record this guard certifies.
416
+ * Every own string-named value is checked through {@link isEligibility}, including
417
+ * non-enumerable values. Inherited and symbol-named members are left unchecked because they
418
+ * are outside the own string-keyed record this guard certifies.
382
419
  *
383
420
  * @param value - The value to test
384
421
  * @returns True if `value` is a non-array object whose own string-named values are
@@ -390,8 +427,10 @@ import { Subject } from '@orkestrel/reason';
390
427
  * Determines whether a value is an open result-side {@link Finding}.
391
428
  *
392
429
  * @remarks
393
- * Unknown members are admitted. Optional `scope` and `message` members may be absent or
394
- * `undefined`; a present defined value must be a string.
430
+ * Every published member is checked, including each nested {@link Premise}. Optional `scope`
431
+ * and `message` members may be absent or `undefined`; a present defined value must be a
432
+ * string. Unknown members are admitted, because a foreign result implementation may add them
433
+ * without changing the published contract.
395
434
  *
396
435
  * @param value - The value to test
397
436
  * @returns True if every published finding member is valid; false otherwise
@@ -402,9 +441,11 @@ import { Subject } from '@orkestrel/reason';
402
441
  * Determines whether a value is an open result-side {@link Premise}.
403
442
  *
404
443
  * @remarks
405
- * `expected` is unchecked and may be absent or contain any value because its published type is
406
- * `unknown`. `actual` is unchecked and may be absent or contain any value for the same reason.
407
- * Unknown members are admitted.
444
+ * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked
445
+ * when they are defined. `expected` is unchecked and may be absent or contain any value
446
+ * because its published type is `unknown`. `actual` is unchecked and may be absent or contain
447
+ * any value for the same reason. Unknown members are admitted, because an open result admits
448
+ * what the published contract does not name.
408
449
  *
409
450
  * @param value - The value to test
410
451
  * @returns True if every checked premise member follows its published type; false otherwise
@@ -414,6 +455,11 @@ import { Subject } from '@orkestrel/reason';
414
455
  /**
415
456
  * Determines whether a value is an exact {@link QualificationDefinition} record.
416
457
  *
458
+ * @remarks
459
+ * Every authored definition member, the nested pass and ruling shapes, and the complete key
460
+ * set are checked. Nothing is left unchecked, because this package owns the authored input
461
+ * record.
462
+ *
417
463
  * @param value - The value to test
418
464
  * @returns True if `value` carries every definition member and no unknown key; false otherwise
419
465
  */
@@ -422,6 +468,10 @@ import { Subject } from '@orkestrel/reason';
422
468
  /**
423
469
  * Determines whether a value is a {@link QualificationEffect} literal.
424
470
  *
471
+ * @remarks
472
+ * The guard is total and leaves nothing unchecked: a scalar union has no open or exact
473
+ * axis.
474
+ *
425
475
  * @param value - The value to test
426
476
  * @returns True if `value` is one of the effect literals; false otherwise
427
477
  */
@@ -430,6 +480,11 @@ import { Subject } from '@orkestrel/reason';
430
480
  /**
431
481
  * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
432
482
  *
483
+ * @remarks
484
+ * The guard is exact and requires either complete reason shape, the quantitative definition or
485
+ * the logical one. Nothing is left unchecked, because the reason package owns both authored
486
+ * input shapes.
487
+ *
433
488
  * @param value - The value to test
434
489
  * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise
435
490
  */
@@ -440,7 +495,10 @@ import { Subject } from '@orkestrel/reason';
440
495
  *
441
496
  * @remarks
442
497
  * This guard is result-postured for values returned through a borrowed qualifier interface. It
443
- * admits unknown members and class instances while checking the complete published result closure.
498
+ * checks the complete published result closure — eligibility, scope values, findings,
499
+ * derivations, success, and the audit trail — and admits unknown members and class instances,
500
+ * because a borrowed `QualifierInterface` implementation may add them beyond the
501
+ * published contract.
444
502
  *
445
503
  * @param value - The value to test
446
504
  * @returns True if every published qualification-result member is valid; false otherwise
@@ -458,6 +516,10 @@ import { Subject } from '@orkestrel/reason';
458
516
  /**
459
517
  * Determines whether a value is an exact {@link Ruling} record.
460
518
  *
519
+ * @remarks
520
+ * Every authored ruling member and the complete key set are checked. Nothing is left
521
+ * unchecked, because this package owns the authored input record.
522
+ *
461
523
  * @param value - The value to test
462
524
  * @returns True if `value` carries every ruling member and no unknown key; false otherwise
463
525
  */
@@ -506,10 +568,13 @@ import { Subject } from '@orkestrel/reason';
506
568
  export declare function mergeQualificationContext(context: QualificationContext, id: string, projection: QualificationProjection): QualificationContext;
507
569
 
508
570
  /**
509
- * Represents display-neutral evidence for one condition, in one of two authoring modes.
510
- * A CHECKED premise carries `field` and `comparison`; a DESCRIBED premise
571
+ * Represents display-neutral evidence for one condition, authored as a checked or a
572
+ * described premise.
573
+ *
574
+ * @remarks
575
+ * A checked premise carries `field` and `comparison`; a described premise
511
576
  * carries neither and renders from `description` instead. The checked form
512
- * renders only when `field` and `comparison` are BOTH present, and
577
+ * renders only when `field` and `comparison` are both present, and
513
578
  * `description` then goes unused; a premise missing either half of the
514
579
  * checked pair renders as described instead. `met` is three-state: `true`
515
580
  * (met), `false` (not met), or absent (not evaluated, rendered as unknown).
@@ -524,7 +589,10 @@ import { Subject } from '@orkestrel/reason';
524
589
  readonly met?: boolean;
525
590
  }
526
591
 
527
- /** Names the reserved internal projection namespace a pass's working projection is written under. */
592
+ /**
593
+ * Names `'qualification'`, the reserved internal projection namespace a pass's working
594
+ * projection is written under.
595
+ */
528
596
  export declare const QUALIFICATION_KEY = "qualification";
529
597
 
530
598
  /** Represents the internal projection record stored under `QUALIFICATION_KEY`. */
@@ -588,8 +656,9 @@ import { Subject } from '@orkestrel/reason';
588
656
  * Runs ordered passes over one reason engine and returns eligibility.
589
657
  *
590
658
  * @remarks
591
- * The engine is OWNED when self-created (destroyed on `destroy()`) and borrowed
592
- * when injected (never destroyed). `qualify` builds its working subject through
659
+ * The engine is owned when self-created (destroyed on `destroy()`) and borrowed
660
+ * when injected (never destroyed). Semantic validation runs before the first pass
661
+ * whenever the `validate` option is on. `qualify` builds its working subject through
593
662
  * copy-on-write overlays only — the caller's `subject` is never mutated, and the
594
663
  * qualifier's internal projection is discarded after each call.
595
664
  */
@@ -695,7 +764,8 @@ import { Subject } from '@orkestrel/reason';
695
764
  }
696
765
 
697
766
  /**
698
- * Represents a coded programmer error thrown by the qualifier layer.
767
+ * Represents a coded programmer error thrown by the qualifier layer, carrying a
768
+ * `QualifierErrorCode` and an optional context record.
699
769
  *
700
770
  * @remarks
701
771
  * `DEFINITION` — a qualification definition failed semantic validation; when
@@ -827,7 +897,7 @@ import { Subject } from '@orkestrel/reason';
827
897
  }
828
898
 
829
899
  /**
830
- * Carries the options for `createQualifier` / the `Qualifier` constructor.
900
+ * Carries the options for `createQualifier` and the `Qualifier` constructor.
831
901
  *
832
902
  * @remarks
833
903
  * `engine` injects a reason engine that stays caller-owned and is never destroyed; an
@@ -917,7 +987,7 @@ import { Subject } from '@orkestrel/reason';
917
987
  * Renders one {@link Premise} into a display-neutral sentence.
918
988
  *
919
989
  * @remarks
920
- * The checked form renders only when `field` and `comparison` are BOTH
990
+ * The checked form renders only when `field` and `comparison` are both
921
991
  * present, and `description` then goes unused; a premise missing either half
922
992
  * of the checked pair renders as described instead.
923
993
  *
@@ -946,7 +1016,7 @@ import { Subject } from '@orkestrel/reason';
946
1016
  * A reason `Bounds`-shaped record (the `between` / `outside` expected value)
947
1017
  * renders its present sides joined with the word `and`; an absent side is
948
1018
  * omitted. A finite number and everything else fall back to plain `String()` —
949
- * a finite number is NOT grouped here (grouping is {@link interpolateMessage}'s
1019
+ * a finite number is not grouped here (grouping is {@link interpolateMessage}'s
950
1020
  * message-token-only behavior).
951
1021
  *
952
1022
  * @param value - The value to render
@@ -971,7 +1041,7 @@ import { Subject } from '@orkestrel/reason';
971
1041
  * A reason rule result carries only booleans, so this is the qualifier's own
972
1042
  * premise-audit projection: each authored premise expression is flattened to its
973
1043
  * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through
974
- * the injected `evaluator`. A membership check (`any` / `none`) over an EMPTY
1044
+ * the injected `evaluator`. A membership check (`any` / `none`) over an empty
975
1045
  * array value is content-free and is skipped.
976
1046
  *
977
1047
  * @param rule - The authored rule
@@ -1018,7 +1088,7 @@ import { Subject } from '@orkestrel/reason';
1018
1088
  *
1019
1089
  * @remarks
1020
1090
  * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated
1021
- * against the SAME subject snapshot the pass evaluated, so a conclusion the pass
1091
+ * against the same subject snapshot the pass evaluated, so a conclusion the pass
1022
1092
  * derived can never satisfy its own reported premise. The message interpolates
1023
1093
  * against that snapshot.
1024
1094
  *
@@ -2,17 +2,26 @@ import { arrayOf, isBoolean, isFiniteNumber, isNumber, isRecord, isString, liter
2
2
  import { createEvaluator, createLogicalReasoner, createQuantitativeReasoner, createReason, extractAtoms, extractConclusions, findDuplicates, formatField, isBounds, isComparison, isFieldPath, isLogicalDefinition, isQuantitativeDefinition, isReasonError } from "@orkestrel/reason";
3
3
  import { Emitter } from "@orkestrel/emitter";
4
4
  //#region src/core/constants.ts
5
- /** Holds the default definition validation policy for `createQualifier` / `Qualifier.qualify`. */
5
+ /**
6
+ * Holds the default definition validation policy for `createQualifier` and
7
+ * `Qualifier.qualify`, `true`.
8
+ */
6
9
  var DEFAULT_QUALIFIER_VALIDATE = true;
7
- /** Names the reserved internal projection namespace a pass's working projection is written under. */
10
+ /**
11
+ * Names `'qualification'`, the reserved internal projection namespace a pass's working
12
+ * projection is written under.
13
+ */
8
14
  var QUALIFICATION_KEY = "qualification";
9
- /** Lists the eligibility severities in order — most to least severe. */
15
+ /** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */
10
16
  var ELIGIBILITY_PRECEDENCE = Object.freeze([
11
17
  "ineligible",
12
18
  "referral",
13
19
  "eligible"
14
20
  ]);
15
- /** Maps each {@link QualificationEffect} to its eligibility impact; `condition` remains eligible. */
21
+ /**
22
+ * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to
23
+ * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.
24
+ */
16
25
  var EFFECT_ELIGIBILITIES = Object.freeze({
17
26
  restriction: "ineligible",
18
27
  referral: "referral",
@@ -21,7 +30,8 @@ var EFFECT_ELIGIBILITIES = Object.freeze({
21
30
  //#endregion
22
31
  //#region src/core/errors.ts
23
32
  /**
24
- * Represents a coded programmer error thrown by the qualifier layer.
33
+ * Represents a coded programmer error thrown by the qualifier layer, carrying a
34
+ * `QualifierErrorCode` and an optional context record.
25
35
  *
26
36
  * @remarks
27
37
  * `DEFINITION` — a qualification definition failed semantic validation; when
@@ -59,6 +69,10 @@ function isQualifierError(value) {
59
69
  /**
60
70
  * Determines whether a value is an {@link Eligibility} literal.
61
71
  *
72
+ * @remarks
73
+ * The guard is total and leaves nothing unchecked: a scalar union has no open or exact
74
+ * axis.
75
+ *
62
76
  * @param value - The value to test
63
77
  * @returns True if `value` is one of the eligibility literals; false otherwise
64
78
  */
@@ -66,6 +80,10 @@ var isEligibility = literalOf("eligible", "ineligible", "referral");
66
80
  /**
67
81
  * Determines whether a value is a {@link QualificationEffect} literal.
68
82
  *
83
+ * @remarks
84
+ * The guard is total and leaves nothing unchecked: a scalar union has no open or exact
85
+ * axis.
86
+ *
69
87
  * @param value - The value to test
70
88
  * @returns True if `value` is one of the effect literals; false otherwise
71
89
  */
@@ -74,8 +92,9 @@ var isQualificationEffect = literalOf("restriction", "referral", "condition");
74
92
  * Determines whether a value is an open string-keyed record of {@link Eligibility} values.
75
93
  *
76
94
  * @remarks
77
- * Every own string-named property is checked, including non-enumerable properties. Inherited and
78
- * symbol-named members are left unchecked because they are outside the record this guard certifies.
95
+ * Every own string-named value is checked through {@link isEligibility}, including
96
+ * non-enumerable values. Inherited and symbol-named members are left unchecked because they
97
+ * are outside the own string-keyed record this guard certifies.
79
98
  *
80
99
  * @param value - The value to test
81
100
  * @returns True if `value` is a non-array object whose own string-named values are
@@ -88,9 +107,11 @@ function isEligibilityRecord(value) {
88
107
  * Determines whether a value is an open result-side {@link Premise}.
89
108
  *
90
109
  * @remarks
91
- * `expected` is unchecked and may be absent or contain any value because its published type is
92
- * `unknown`. `actual` is unchecked and may be absent or contain any value for the same reason.
93
- * Unknown members are admitted.
110
+ * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked
111
+ * when they are defined. `expected` is unchecked and may be absent or contain any value
112
+ * because its published type is `unknown`. `actual` is unchecked and may be absent or contain
113
+ * any value for the same reason. Unknown members are admitted, because an open result admits
114
+ * what the published contract does not name.
94
115
  *
95
116
  * @param value - The value to test
96
117
  * @returns True if every checked premise member follows its published type; false otherwise
@@ -114,8 +135,10 @@ function isPremise(value) {
114
135
  * Determines whether a value is an open result-side {@link Finding}.
115
136
  *
116
137
  * @remarks
117
- * Unknown members are admitted. Optional `scope` and `message` members may be absent or
118
- * `undefined`; a present defined value must be a string.
138
+ * Every published member is checked, including each nested {@link Premise}. Optional `scope`
139
+ * and `message` members may be absent or `undefined`; a present defined value must be a
140
+ * string. Unknown members are admitted, because a foreign result implementation may add them
141
+ * without changing the published contract.
119
142
  *
120
143
  * @param value - The value to test
121
144
  * @returns True if every published finding member is valid; false otherwise
@@ -136,8 +159,10 @@ function isFinding(value) {
136
159
  * Determines whether a value is an open result-side {@link Derivation}.
137
160
  *
138
161
  * @remarks
139
- * Unknown members are admitted. `value` follows the published `number` type, including `NaN` and
140
- * infinities.
162
+ * Every published member is checked: `value` follows the published `number` type, including
163
+ * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are
164
+ * admitted, because a foreign result implementation may add them without changing the
165
+ * published contract.
141
166
  *
142
167
  * @param value - The value to test
143
168
  * @returns True if every published derivation member is valid; false otherwise
@@ -156,7 +181,10 @@ function isDerivation(value) {
156
181
  *
157
182
  * @remarks
158
183
  * This guard is result-postured for values returned through a borrowed qualifier interface. It
159
- * admits unknown members and class instances while checking the complete published result closure.
184
+ * checks the complete published result closure — eligibility, scope values, findings,
185
+ * derivations, success, and the audit trail — and admits unknown members and class instances,
186
+ * because a borrowed `QualifierInterface` implementation may add them beyond the
187
+ * published contract.
160
188
  *
161
189
  * @param value - The value to test
162
190
  * @returns True if every published qualification-result member is valid; false otherwise
@@ -177,6 +205,11 @@ function isQualificationResult(value) {
177
205
  /**
178
206
  * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
179
207
  *
208
+ * @remarks
209
+ * The guard is exact and requires either complete reason shape, the quantitative definition or
210
+ * the logical one. Nothing is left unchecked, because the reason package owns both authored
211
+ * input shapes.
212
+ *
180
213
  * @param value - The value to test
181
214
  * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise
182
215
  */
@@ -186,6 +219,10 @@ function isQualificationPass(value) {
186
219
  /**
187
220
  * Determines whether a value is an exact {@link Ruling} record.
188
221
  *
222
+ * @remarks
223
+ * Every authored ruling member and the complete key set are checked. Nothing is left
224
+ * unchecked, because this package owns the authored input record.
225
+ *
189
226
  * @param value - The value to test
190
227
  * @returns True if `value` carries every ruling member and no unknown key; false otherwise
191
228
  */
@@ -202,6 +239,11 @@ function isRuling(value) {
202
239
  /**
203
240
  * Determines whether a value is an exact {@link QualificationDefinition} record.
204
241
  *
242
+ * @remarks
243
+ * Every authored definition member, the nested pass and ruling shapes, and the complete key
244
+ * set are checked. Nothing is left unchecked, because this package owns the authored input
245
+ * record.
246
+ *
205
247
  * @param value - The value to test
206
248
  * @returns True if `value` carries every definition member and no unknown key; false otherwise
207
249
  */
@@ -226,10 +268,10 @@ function isQualificationDefinition(value) {
226
268
  *
227
269
  * @remarks
228
270
  * Each token is split on `.` into a `FieldPath` array and resolved with
229
- * `resolveField` (a plain string field is ONE key, never dot-split — the split
271
+ * `resolveField` (a plain string field is one key, never dot-split — the split
230
272
  * here is the token-to-path bridge). A finite number renders with `en-US`
231
273
  * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.
232
- * An UNRESOLVED path renders as the empty string.
274
+ * An unresolved path renders as the empty string.
233
275
  *
234
276
  * @param template - The message template carrying `{{dotted.path}}` tokens
235
277
  * @param subject - The record tokens resolve against
@@ -286,7 +328,7 @@ function renderComparison(comparison) {
286
328
  * A reason `Bounds`-shaped record (the `between` / `outside` expected value)
287
329
  * renders its present sides joined with the word `and`; an absent side is
288
330
  * omitted. A finite number and everything else fall back to plain `String()` —
289
- * a finite number is NOT grouped here (grouping is {@link interpolateMessage}'s
331
+ * a finite number is not grouped here (grouping is {@link interpolateMessage}'s
290
332
  * message-token-only behavior).
291
333
  *
292
334
  * @param value - The value to render
@@ -315,7 +357,7 @@ function renderValue(value) {
315
357
  * Renders one {@link Premise} into a display-neutral sentence.
316
358
  *
317
359
  * @remarks
318
- * The checked form renders only when `field` and `comparison` are BOTH
360
+ * The checked form renders only when `field` and `comparison` are both
319
361
  * present, and `description` then goes unused; a premise missing either half
320
362
  * of the checked pair renders as described instead.
321
363
  *
@@ -380,7 +422,7 @@ function checkToPremise(check, result, labels) {
380
422
  * A reason rule result carries only booleans, so this is the qualifier's own
381
423
  * premise-audit projection: each authored premise expression is flattened to its
382
424
  * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through
383
- * the injected `evaluator`. A membership check (`any` / `none`) over an EMPTY
425
+ * the injected `evaluator`. A membership check (`any` / `none`) over an empty
384
426
  * array value is content-free and is skipped.
385
427
  *
386
428
  * @param rule - The authored rule
@@ -549,7 +591,7 @@ function mergeQualificationContext(context, id, projection) {
549
591
  *
550
592
  * @remarks
551
593
  * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated
552
- * against the SAME subject snapshot the pass evaluated, so a conclusion the pass
594
+ * against the same subject snapshot the pass evaluated, so a conclusion the pass
553
595
  * derived can never satisfy its own reported premise. The message interpolates
554
596
  * against that snapshot.
555
597
  *
@@ -862,8 +904,9 @@ function describeUnreadDerivations(definition) {
862
904
  * Runs ordered passes over one reason engine and returns eligibility.
863
905
  *
864
906
  * @remarks
865
- * The engine is OWNED when self-created (destroyed on `destroy()`) and borrowed
866
- * when injected (never destroyed). `qualify` builds its working subject through
907
+ * The engine is owned when self-created (destroyed on `destroy()`) and borrowed
908
+ * when injected (never destroyed). Semantic validation runs before the first pass
909
+ * whenever the `validate` option is on. `qualify` builds its working subject through
867
910
  * copy-on-write overlays only — the caller's `subject` is never mutated, and the
868
911
  * qualifier's internal projection is discarded after each call.
869
912
  */
@@ -1091,10 +1134,10 @@ var Qualifier = class {
1091
1134
  //#endregion
1092
1135
  //#region src/core/factories.ts
1093
1136
  /**
1094
- * Creates one qualifier over a reason engine.
1137
+ * Creates one {@link QualifierInterface} over a reason engine.
1095
1138
  *
1096
1139
  * @remarks
1097
- * A standalone qualifier creates and OWNS one shared quantitative-plus-logical
1140
+ * A standalone qualifier creates and owns one shared quantitative-plus-logical
1098
1141
  * reason engine, destroying it on `destroy()`. An injected `options.engine`
1099
1142
  * remains caller-owned and is never destroyed. `validate` runs semantic
1100
1143
  * validation before every qualification. Default: `true`. `labels` overrides the
@@ -1104,12 +1147,37 @@ var Qualifier = class {
1104
1147
  * @param options - Optional injected engine, validation, labels, and emitter hooks
1105
1148
  * @returns A {@link QualifierInterface}
1106
1149
  *
1107
- * @example
1150
+ * @example Create a qualifier
1108
1151
  * ```ts
1109
- * import { createQualifier } from '@orkestrel/qualifier'
1152
+ * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'
1153
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
1154
+ *
1155
+ * const gates = createLogicalDefinition('gates', 'Eligibility gates', [
1156
+ * createRule(
1157
+ * 'licensed',
1158
+ * [createAtom('licensed', 'equals', false)],
1159
+ * createAtom('blocked', 'equals', true),
1160
+ * ),
1161
+ * ])
1162
+ *
1163
+ * const bare = createRuling('license', 'gates', 'licensed', 'restriction')
1164
+ * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {
1165
+ * message: 'A license is required',
1166
+ * })
1167
+ *
1168
+ * 'message' in bare // false — an absent optional key is omitted, never written as undefined
1169
+ * messaged.message // 'A license is required'
1170
+ *
1171
+ * const passes = [gates]
1172
+ * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {
1173
+ * rulings: [messaged],
1174
+ * })
1175
+ *
1176
+ * 'description' in definition // false
1177
+ * definition.passes === passes // false — the factory copies what it is handed
1110
1178
  *
1111
1179
  * const qualifier = createQualifier()
1112
- * qualifier.qualify({ id: 'risk-1' }, definition)
1180
+ * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)
1113
1181
  * qualifier.destroy()
1114
1182
  * ```
1115
1183
  */
@@ -1117,10 +1185,9 @@ function createQualifier(options) {
1117
1185
  return new Qualifier(options);
1118
1186
  }
1119
1187
  /**
1120
- * Creates a {@link QualificationDefinition}.
1188
+ * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.
1121
1189
  *
1122
1190
  * @remarks
1123
- * Returns a fresh top-level definition, omitting absent optional keys;
1124
1191
  * `passes` and `rulings` arrays are copied, and record `metadata` is
1125
1192
  * shallow-copied so nested values are not deep-cloned.
1126
1193
  *
@@ -1148,7 +1215,7 @@ function createQualificationDefinition(id, name, passes, input) {
1148
1215
  };
1149
1216
  }
1150
1217
  /**
1151
- * Creates a {@link Ruling} — one authored consequence for one rule in one pass.
1218
+ * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.
1152
1219
  *
1153
1220
  * @param id - The ruling id
1154
1221
  * @param pass - The logical pass id the rule lives in