@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.
- package/README.md +13 -8
- package/dist/src/core/index.cjs +98 -31
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -53
- package/dist/src/core/index.d.ts +123 -53
- package/dist/src/core/index.js +98 -31
- package/dist/src/core/index.js.map +1 -1
- package/package.json +15 -16
package/dist/src/core/index.d.ts
CHANGED
|
@@ -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
|
|
96
|
+
* Creates one {@link QualifierInterface} over a reason engine.
|
|
98
97
|
*
|
|
99
98
|
* @remarks
|
|
100
|
-
* A standalone qualifier creates and
|
|
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}
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
381
|
-
* symbol-named members are left unchecked because they
|
|
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
|
-
*
|
|
394
|
-
* `undefined`; a present defined value must be a
|
|
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
|
-
* `
|
|
406
|
-
*
|
|
407
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
510
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
592
|
-
* when injected (never destroyed).
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
*
|
package/dist/src/core/index.js
CHANGED
|
@@ -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
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Holds the default definition validation policy for `createQualifier` and
|
|
7
|
+
* `Qualifier.qualify`, `true`.
|
|
8
|
+
*/
|
|
6
9
|
var DEFAULT_QUALIFIER_VALIDATE = true;
|
|
7
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
78
|
-
* symbol-named members are left unchecked because they
|
|
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
|
-
* `
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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
|
-
*
|
|
118
|
-
* `undefined`; a present defined value must be a
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
866
|
-
* when injected (never destroyed).
|
|
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
|
|
1137
|
+
* Creates one {@link QualifierInterface} over a reason engine.
|
|
1095
1138
|
*
|
|
1096
1139
|
* @remarks
|
|
1097
|
-
* A standalone qualifier creates and
|
|
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}
|
|
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
|