@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.
@@ -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 pre-rating outcome axis. */
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 duplicate ids in a list, once each.
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 ids - The ids to scan
175
- * @returns A fresh list of ids that appear more than once
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 findDuplicateIds(ids: readonly string[]): readonly string[];
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 interface QualificationValidationResult {
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
- * pre-rating eligibility.
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 pre-rating eligibility. */
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 rating scope and message template
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