@orkestrel/qualifier 0.0.1 → 0.0.3
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 +1 -1
- package/dist/src/core/index.cjs +312 -85
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +188 -14
- package/dist/src/core/index.d.ts +188 -14
- package/dist/src/core/index.js +311 -86
- package/dist/src/core/index.js.map +1 -1
- package/package.json +13 -13
package/dist/src/core/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { QuantitativeDefinition } from '@orkestrel/reason';
|
|
|
14
14
|
import { QuantitativeResult } from '@orkestrel/reason';
|
|
15
15
|
import { ReasonInterface } from '@orkestrel/reason';
|
|
16
16
|
import { ReasonResult } from '@orkestrel/reason';
|
|
17
|
+
import { ReasonValidationResult } from '@orkestrel/reason';
|
|
17
18
|
import { Rule } from '@orkestrel/reason';
|
|
18
19
|
import { Subject } from '@orkestrel/reason';
|
|
19
20
|
|
|
@@ -23,6 +24,13 @@ import { Subject } from '@orkestrel/reason';
|
|
|
23
24
|
* @param value - The candidate subject to validate
|
|
24
25
|
* @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or
|
|
25
26
|
* when it already carries the reserved `qualification` key
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { assertSubject } from '@orkestrel/qualifier'
|
|
31
|
+
*
|
|
32
|
+
* assertSubject({ id: 's1' }) // narrows to Subject
|
|
33
|
+
* ```
|
|
26
34
|
*/
|
|
27
35
|
export declare function assertSubject(value: unknown): asserts value is Subject;
|
|
28
36
|
|
|
@@ -86,6 +94,14 @@ import { Subject } from '@orkestrel/reason';
|
|
|
86
94
|
* @param findings - The resolved findings
|
|
87
95
|
* @param failed - Whether an operational pass failure occurred
|
|
88
96
|
* @returns The most severe global eligibility
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { deriveFindingEligibility } from '@orkestrel/qualifier'
|
|
101
|
+
*
|
|
102
|
+
* const finding = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const, applied: true, premises: [] }
|
|
103
|
+
* deriveFindingEligibility([finding]) // 'ineligible'
|
|
104
|
+
* ```
|
|
89
105
|
*/
|
|
90
106
|
export declare function deriveFindingEligibility(findings: Finding[], failed?: boolean): Eligibility;
|
|
91
107
|
|
|
@@ -99,6 +115,14 @@ import { Subject } from '@orkestrel/reason';
|
|
|
99
115
|
*
|
|
100
116
|
* @param findings - The resolved findings
|
|
101
117
|
* @returns A fresh record of scope to its combined eligibility
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { deriveScopeEligibilities } from '@orkestrel/qualifier'
|
|
122
|
+
*
|
|
123
|
+
* const finding = { id: 'coastal', pass: 'wind', rule: 'coastal', effect: 'restriction' as const, scope: 'wind', applied: true, premises: [] }
|
|
124
|
+
* deriveScopeEligibilities([finding]) // { wind: 'ineligible' }
|
|
125
|
+
* ```
|
|
102
126
|
*/
|
|
103
127
|
export declare function deriveScopeEligibilities(findings: Finding[]): Readonly<Record<string, Eligibility>>;
|
|
104
128
|
|
|
@@ -162,19 +186,37 @@ import { Subject } from '@orkestrel/reason';
|
|
|
162
186
|
/** The eligibility impact of each {@link QualificationEffect}; `condition` remains eligible. */
|
|
163
187
|
export declare const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>>;
|
|
164
188
|
|
|
165
|
-
/** The
|
|
189
|
+
/** The eligibility outcome axis. */
|
|
166
190
|
export declare type Eligibility = 'eligible' | 'ineligible' | 'referral';
|
|
167
191
|
|
|
168
192
|
/** Eligibility severity order — most to least severe. */
|
|
169
193
|
export declare const ELIGIBILITY_PRECEDENCE: readonly Eligibility[];
|
|
170
194
|
|
|
171
195
|
/**
|
|
172
|
-
* Find
|
|
196
|
+
* Find logical passes carrying no rulings.
|
|
197
|
+
*
|
|
198
|
+
* @remarks
|
|
199
|
+
* A logical pass with no matching ruling can never contribute a finding, so its
|
|
200
|
+
* rules only ever influence later passes' reads — this is very likely an authoring
|
|
201
|
+
* oversight.
|
|
173
202
|
*
|
|
174
|
-
* @param
|
|
175
|
-
* @returns A fresh list of
|
|
203
|
+
* @param definition - The qualification definition to check
|
|
204
|
+
* @returns A fresh list of warning messages
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { findEmptyLogicalPasses, qualificationDefinition } from '@orkestrel/qualifier'
|
|
209
|
+
* import { atom, logicalDefinition, rule } from '@orkestrel/reason'
|
|
210
|
+
*
|
|
211
|
+
* const gates = logicalDefinition('gates', 'Gates', [
|
|
212
|
+
* rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
|
|
213
|
+
* ])
|
|
214
|
+
* const definition = qualificationDefinition('standard', 'Standard', [gates])
|
|
215
|
+
*
|
|
216
|
+
* findEmptyLogicalPasses(definition) // ["Logical pass 'gates' has no rulings"]
|
|
217
|
+
* ```
|
|
176
218
|
*/
|
|
177
|
-
export declare function
|
|
219
|
+
export declare function findEmptyLogicalPasses(definition: QualificationDefinition): readonly string[];
|
|
178
220
|
|
|
179
221
|
/** One resolved ruling. */
|
|
180
222
|
export declare interface Finding {
|
|
@@ -194,6 +236,22 @@ import { Subject } from '@orkestrel/reason';
|
|
|
194
236
|
*
|
|
195
237
|
* @param definition - The qualification definition to check
|
|
196
238
|
* @returns A fresh list of reference-error messages
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* import { findMissingReferences, qualificationDefinition, rulingDefinition } from '@orkestrel/qualifier'
|
|
243
|
+
* import { atom, logicalDefinition, rule } from '@orkestrel/reason'
|
|
244
|
+
*
|
|
245
|
+
* const gates = logicalDefinition('gates', 'Gates', [
|
|
246
|
+
* rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
|
|
247
|
+
* ])
|
|
248
|
+
* const definition = qualificationDefinition('standard', 'Standard', [gates], {
|
|
249
|
+
* rulings: [rulingDefinition('license', 'gates', 'absent', 'restriction')],
|
|
250
|
+
* })
|
|
251
|
+
*
|
|
252
|
+
* findMissingReferences(definition)
|
|
253
|
+
* // ["Ruling 'license' references missing rule 'absent' in pass 'gates'"]
|
|
254
|
+
* ```
|
|
197
255
|
*/
|
|
198
256
|
export declare function findMissingReferences(definition: QualificationDefinition): readonly string[];
|
|
199
257
|
|
|
@@ -218,11 +276,46 @@ import { Subject } from '@orkestrel/reason';
|
|
|
218
276
|
*/
|
|
219
277
|
export declare function findRule(definition: LogicalDefinition, id: string): Rule | undefined;
|
|
220
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Find quantitative passes never read by a later pass.
|
|
281
|
+
*
|
|
282
|
+
* @remarks
|
|
283
|
+
* A quantitative pass's projected value lives under `qualification.<id>` — if no
|
|
284
|
+
* later logical premise or quantitative factor field reads that key, the
|
|
285
|
+
* derivation is dead weight. Reads are collected per later pass: a logical pass's
|
|
286
|
+
* premise atoms (conclusion atoms are writes, not reads), and a quantitative
|
|
287
|
+
* pass's field/lookup/range factor sources plus factor checks.
|
|
288
|
+
*
|
|
289
|
+
* @param definition - The qualification definition to check
|
|
290
|
+
* @returns A fresh list of warning messages
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* import { findUnreadDerivations, qualificationDefinition } from '@orkestrel/qualifier'
|
|
295
|
+
* import { factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
|
|
296
|
+
*
|
|
297
|
+
* const cap = quantitativeDefinition('cap', 'TIV cap', [
|
|
298
|
+
* factorGroup('limit', 'sum', [staticFactor('base', 500_000)]),
|
|
299
|
+
* ])
|
|
300
|
+
* const definition = qualificationDefinition('standard', 'Standard', [cap])
|
|
301
|
+
*
|
|
302
|
+
* findUnreadDerivations(definition) // ["Quantitative pass 'cap' is never read by a later pass"]
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export declare function findUnreadDerivations(definition: QualificationDefinition): readonly string[];
|
|
306
|
+
|
|
221
307
|
/**
|
|
222
308
|
* Determine whether a subject already owns the reserved {@link QUALIFICATION_KEY}.
|
|
223
309
|
*
|
|
224
310
|
* @param subject - The subject to check
|
|
225
311
|
* @returns `true` when the subject owns the reserved key
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* import { hasReservedKey } from '@orkestrel/qualifier'
|
|
316
|
+
*
|
|
317
|
+
* hasReservedKey({ id: 's1', qualification: {} }) // true
|
|
318
|
+
* ```
|
|
226
319
|
*/
|
|
227
320
|
export declare function hasReservedKey(subject: Subject): boolean;
|
|
228
321
|
|
|
@@ -301,6 +394,30 @@ import { Subject } from '@orkestrel/reason';
|
|
|
301
394
|
*/
|
|
302
395
|
export declare function logicalPremises(rule: Rule, working: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): readonly Premise[];
|
|
303
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Map an engine throw caught while running one pass to a typed {@link QualifierError}.
|
|
399
|
+
*
|
|
400
|
+
* @remarks
|
|
401
|
+
* A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the
|
|
402
|
+
* pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;
|
|
403
|
+
* every other `ReasonError` code (e.g. `MISSING`) and every non-`ReasonError` throw
|
|
404
|
+
* (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw
|
|
405
|
+
* is preserved as `context.cause`.
|
|
406
|
+
*
|
|
407
|
+
* @param error - The value caught from the engine's `reason` call
|
|
408
|
+
* @param pass - The id of the pass that was running
|
|
409
|
+
* @returns A fresh, typed {@link QualifierError}
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```ts
|
|
413
|
+
* import { mapEngineError } from '@orkestrel/qualifier'
|
|
414
|
+
* import { ReasonError } from '@orkestrel/reason'
|
|
415
|
+
*
|
|
416
|
+
* mapEngineError(new ReasonError('MISSING', 'No reasoner registered'), 'cap').code // 'ENGINE'
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
export declare function mapEngineError(error: unknown, pass: string): QualifierError;
|
|
420
|
+
|
|
304
421
|
/**
|
|
305
422
|
* Copy-on-write merge one pass projection into the context.
|
|
306
423
|
*
|
|
@@ -308,6 +425,14 @@ import { Subject } from '@orkestrel/reason';
|
|
|
308
425
|
* @param id - The pass id to project under
|
|
309
426
|
* @param projection - The pass's working projection
|
|
310
427
|
* @returns A fresh context with the projection added
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* import { mergeQualificationContext } from '@orkestrel/qualifier'
|
|
432
|
+
*
|
|
433
|
+
* mergeQualificationContext({ cap: 500_000 }, 'excess', 250_000)
|
|
434
|
+
* // { cap: 500000, excess: 250000 }
|
|
435
|
+
* ```
|
|
311
436
|
*/
|
|
312
437
|
export declare function mergeQualificationContext(context: QualificationContext, id: string, projection: QualificationProjection): QualificationContext;
|
|
313
438
|
|
|
@@ -416,19 +541,22 @@ import { Subject } from '@orkestrel/reason';
|
|
|
416
541
|
*
|
|
417
542
|
* @param context - The accumulated projection context
|
|
418
543
|
* @returns A record carrying the context under the reserved key
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```ts
|
|
547
|
+
* import { qualificationToRecord } from '@orkestrel/qualifier'
|
|
548
|
+
*
|
|
549
|
+
* qualificationToRecord({ cap: 500_000 }) // { qualification: { cap: 500000 } }
|
|
550
|
+
* ```
|
|
419
551
|
*/
|
|
420
552
|
export declare function qualificationToRecord(context: QualificationContext): Readonly<Record<string, QualificationContext>>;
|
|
421
553
|
|
|
422
554
|
/** Semantic definition validation. */
|
|
423
|
-
export declare
|
|
424
|
-
readonly valid: boolean;
|
|
425
|
-
readonly errors: readonly string[];
|
|
426
|
-
readonly warnings: readonly string[];
|
|
427
|
-
}
|
|
555
|
+
export declare type QualificationValidationResult = ReasonValidationResult;
|
|
428
556
|
|
|
429
557
|
/**
|
|
430
558
|
* A qualifier — runs ordered passes over one reason engine and returns
|
|
431
|
-
*
|
|
559
|
+
* eligibility.
|
|
432
560
|
*
|
|
433
561
|
* @remarks
|
|
434
562
|
* The engine is OWNED when self-created (destroyed on `destroy()`) and borrowed
|
|
@@ -452,6 +580,8 @@ import { Subject } from '@orkestrel/reason';
|
|
|
452
580
|
* `DEFINITION` — a qualification definition failed semantic validation.
|
|
453
581
|
* `MISMATCH` — a subject is not a record or already carries the reserved
|
|
454
582
|
* `qualification` key. `DESTROYED` — use of a destroyed qualifier.
|
|
583
|
+
* `ENGINE` — the underlying reason engine threw while running a pass (the
|
|
584
|
+
* original throw is preserved as `context.cause`).
|
|
455
585
|
*/
|
|
456
586
|
export declare class QualifierError extends Error {
|
|
457
587
|
readonly code: QualifierErrorCode;
|
|
@@ -460,7 +590,7 @@ import { Subject } from '@orkestrel/reason';
|
|
|
460
590
|
}
|
|
461
591
|
|
|
462
592
|
/** A coded {@link QualifierError} programmer-error code. */
|
|
463
|
-
export declare type QualifierErrorCode = 'DEFINITION' | 'MISMATCH' | 'DESTROYED';
|
|
593
|
+
export declare type QualifierErrorCode = 'DEFINITION' | 'MISMATCH' | 'DESTROYED' | 'ENGINE';
|
|
464
594
|
|
|
465
595
|
/** The push observation surface of a {@link QualifierInterface} (AGENTS §13). */
|
|
466
596
|
export declare type QualifierEventMap = {
|
|
@@ -470,7 +600,7 @@ import { Subject } from '@orkestrel/reason';
|
|
|
470
600
|
readonly destroy: readonly [];
|
|
471
601
|
};
|
|
472
602
|
|
|
473
|
-
/** One qualifier — owns or borrows one reason engine and returns
|
|
603
|
+
/** One qualifier — owns or borrows one reason engine and returns eligibility. */
|
|
474
604
|
export declare interface QualifierInterface {
|
|
475
605
|
readonly emitter: EmitterInterface<QualifierEventMap>;
|
|
476
606
|
qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
|
|
@@ -493,6 +623,15 @@ import { Subject } from '@orkestrel/reason';
|
|
|
493
623
|
* @param id - The pass id
|
|
494
624
|
* @param result - The evaluated quantitative result
|
|
495
625
|
* @returns A fresh derivation
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```ts
|
|
629
|
+
* import { quantitativeResultToDerivation } from '@orkestrel/qualifier'
|
|
630
|
+
*
|
|
631
|
+
* const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }
|
|
632
|
+
* quantitativeResultToDerivation('cap', result)
|
|
633
|
+
* // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }
|
|
634
|
+
* ```
|
|
496
635
|
*/
|
|
497
636
|
export declare function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation;
|
|
498
637
|
|
|
@@ -508,6 +647,26 @@ import { Subject } from '@orkestrel/reason';
|
|
|
508
647
|
* @param definition - The pass definition that produced the result
|
|
509
648
|
* @param result - The evaluated reason result
|
|
510
649
|
* @returns The pass's working projection
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* ```ts
|
|
653
|
+
* import { reasonResultToProjection } from '@orkestrel/qualifier'
|
|
654
|
+
* import { factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
|
|
655
|
+
*
|
|
656
|
+
* const cap = quantitativeDefinition('cap', 'TIV cap', [
|
|
657
|
+
* factorGroup('limit', 'sum', [staticFactor('base', 500_000)]),
|
|
658
|
+
* ])
|
|
659
|
+
* const result = {
|
|
660
|
+
* reasoning: 'quantitative' as const,
|
|
661
|
+
* value: 500_000,
|
|
662
|
+
* groups: [],
|
|
663
|
+
* count: 1,
|
|
664
|
+
* success: true,
|
|
665
|
+
* trace: [],
|
|
666
|
+
* errors: [],
|
|
667
|
+
* }
|
|
668
|
+
* reasonResultToProjection(cap, result) // 500000
|
|
669
|
+
* ```
|
|
511
670
|
*/
|
|
512
671
|
export declare function reasonResultToProjection(definition: QualificationPass, result: ReasonResult): QualificationProjection;
|
|
513
672
|
|
|
@@ -528,7 +687,7 @@ import { Subject } from '@orkestrel/reason';
|
|
|
528
687
|
* @param pass - The logical pass id the rule lives in
|
|
529
688
|
* @param rule - The rule id whose firing this ruling reacts to
|
|
530
689
|
* @param effect - The eligibility impact when the rule fires
|
|
531
|
-
* @param input - Optional
|
|
690
|
+
* @param input - Optional selection scope and message template
|
|
532
691
|
* @returns A fresh ruling
|
|
533
692
|
*
|
|
534
693
|
* @example
|
|
@@ -563,6 +722,21 @@ import { Subject } from '@orkestrel/reason';
|
|
|
563
722
|
* @param evaluator - The shared reason check evaluator
|
|
564
723
|
* @param labels - Optional field-to-label overrides, keyed by dot-joined field
|
|
565
724
|
* @returns A fresh finding
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* ```ts
|
|
728
|
+
* import { rulingToFinding } from '@orkestrel/qualifier'
|
|
729
|
+
* import { atom, createEvaluator, logicalDefinition, rule } from '@orkestrel/reason'
|
|
730
|
+
*
|
|
731
|
+
* const gates = logicalDefinition('gates', 'Gates', [
|
|
732
|
+
* rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
|
|
733
|
+
* ])
|
|
734
|
+
* const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }
|
|
735
|
+
* const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true], conclusion: true }], count: 1, success: true, trace: [], errors: [] }
|
|
736
|
+
* const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())
|
|
737
|
+
*
|
|
738
|
+
* finding.applied // true
|
|
739
|
+
* ```
|
|
566
740
|
*/
|
|
567
741
|
export declare function rulingToFinding(ruling: Ruling, pass: QualificationPass, result: LogicalResult, subject: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): Finding;
|
|
568
742
|
|