@orkestrel/qualifier 0.0.12 → 0.0.13

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.
@@ -19,7 +19,7 @@ import { Rule } from '@orkestrel/reason';
19
19
  import { Subject } from '@orkestrel/reason';
20
20
 
21
21
  /**
22
- * Assert a value is a valid qualification {@link Subject}, narrowing it in place.
22
+ * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.
23
23
  *
24
24
  * @param value - The candidate subject to validate
25
25
  * @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or
@@ -35,7 +35,28 @@ import { Subject } from '@orkestrel/reason';
35
35
  export declare function assertSubject(value: unknown): asserts value is Subject;
36
36
 
37
37
  /**
38
- * Return the most severe {@link Eligibility} in a list.
38
+ * Builds a {@link Premise} from an authored {@link Check} and its evaluated
39
+ * `CheckResult`.
40
+ *
41
+ * @param check - The authored check
42
+ * @param result - The evaluated check result carrying the resolved `actual` and `met`
43
+ * @param labels - Optional field-to-label overrides, keyed by dot-joined field
44
+ * @returns A fresh premise
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { checkToPremise } from '@orkestrel/qualifier'
49
+ * import { createCheck } from '@orkestrel/reason'
50
+ *
51
+ * const authored = createCheck('age', 'above', 18)
52
+ * checkToPremise(authored, { field: 'age', actual: 25, met: true })
53
+ * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }
54
+ * ```
55
+ */
56
+ export declare function checkToPremise(check: Check, result: CheckResult, labels?: Readonly<Record<string, string>>): Premise;
57
+
58
+ /**
59
+ * Returns the most severe {@link Eligibility} in a list.
39
60
  *
40
61
  * @param eligibilities - The eligibilities to combine
41
62
  * @returns The most severe eligibility, or `'eligible'` for an empty list
@@ -47,15 +68,41 @@ import { Subject } from '@orkestrel/reason';
47
68
  * combineEligibilities(['eligible', 'referral']) // 'referral'
48
69
  * ```
49
70
  */
50
- export declare function combineEligibilities(eligibilities: Eligibility[]): Eligibility;
71
+ export declare function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility;
72
+
73
+ /**
74
+ * Creates a {@link QualificationDefinition}.
75
+ *
76
+ * @remarks
77
+ * Returns a fresh top-level definition, omitting absent optional keys;
78
+ * `passes` and `rulings` arrays are copied, and record `metadata` is
79
+ * shallow-copied so nested values are not deep-cloned.
80
+ *
81
+ * @param id - The definition id
82
+ * @param name - The display name
83
+ * @param passes - The ordered qualification passes
84
+ * @param input - Optional description, rulings, and metadata
85
+ * @returns A fresh qualification definition
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { createQualificationDefinition } from '@orkestrel/qualifier'
90
+ *
91
+ * createQualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })
92
+ * ```
93
+ */
94
+ export declare function createQualificationDefinition(id: string, name: string, passes: readonly QualificationPass[], input?: QualificationInput): QualificationDefinition;
51
95
 
52
96
  /**
53
- * Create one qualifier over a reason engine.
97
+ * Creates one qualifier over a reason engine.
54
98
  *
55
99
  * @remarks
56
100
  * A standalone qualifier creates and OWNS one shared quantitative-plus-logical
57
101
  * reason engine, destroying it on `destroy()`. An injected `options.engine`
58
- * remains caller-owned and is never destroyed. Validation is on by default.
102
+ * remains caller-owned and is never destroyed. `validate` runs semantic
103
+ * validation before every qualification. Default: `true`. `labels` overrides the
104
+ * display name of a premise field, keyed by the dot-joined field path
105
+ * `formatField` produces, and takes precedence over a premise's own `label`.
59
106
  *
60
107
  * @param options - Optional injected engine, validation, labels, and emitter hooks
61
108
  * @returns A {@link QualifierInterface}
@@ -71,10 +118,29 @@ import { Subject } from '@orkestrel/reason';
71
118
  */
72
119
  export declare function createQualifier(options?: QualifierOptions): QualifierInterface;
73
120
 
74
- /** Default definition validation policy for `createQualifier` / `Qualifier.qualify`. */
121
+ /**
122
+ * Creates a {@link Ruling} — one authored consequence for one rule in one pass.
123
+ *
124
+ * @param id - The ruling id
125
+ * @param pass - The logical pass id the rule lives in
126
+ * @param rule - The rule id whose firing this ruling reacts to
127
+ * @param effect - The eligibility impact when the rule fires
128
+ * @param input - Optional selection scope and message template
129
+ * @returns A fresh ruling
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * import { createRuling } from '@orkestrel/qualifier'
134
+ *
135
+ * createRuling('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })
136
+ * ```
137
+ */
138
+ export declare function createRuling(id: string, pass: string, rule: string, effect: QualificationEffect, input?: RulingInput): Ruling;
139
+
140
+ /** Holds the default definition validation policy for `createQualifier` / `Qualifier.qualify`. */
75
141
  export declare const DEFAULT_QUALIFIER_VALIDATE = true;
76
142
 
77
- /** One quantitative pass's audit result. */
143
+ /** Represents one quantitative pass's audit result. */
78
144
  export declare interface Derivation {
79
145
  readonly id: string;
80
146
  readonly value: number;
@@ -84,7 +150,7 @@ import { Subject } from '@orkestrel/reason';
84
150
  }
85
151
 
86
152
  /**
87
- * Derive global eligibility from applied, unscoped findings.
153
+ * Derives global eligibility from applied, unscoped findings.
88
154
  *
89
155
  * @remarks
90
156
  * Only findings that both applied and carry no `scope` shape global eligibility.
@@ -92,7 +158,8 @@ import { Subject } from '@orkestrel/reason';
92
158
  * `failed`.
93
159
  *
94
160
  * @param findings - The resolved findings
95
- * @param failed - Whether an operational pass failure occurred
161
+ * @param failed - If `true`, adds a synthetic `referral` impact so an operational failure
162
+ * fails closed; if `false`, derives eligibility from the findings alone. Default: `false`
96
163
  * @returns The most severe global eligibility
97
164
  *
98
165
  * @example
@@ -103,10 +170,10 @@ import { Subject } from '@orkestrel/reason';
103
170
  * deriveFindingEligibility([finding]) // 'ineligible'
104
171
  * ```
105
172
  */
106
- export declare function deriveFindingEligibility(findings: Finding[], failed?: boolean): Eligibility;
173
+ export declare function deriveFindingEligibility(findings: readonly Finding[], failed?: boolean): Eligibility;
107
174
 
108
175
  /**
109
- * Derive one eligibility per finding scope.
176
+ * Derives one eligibility per finding scope.
110
177
  *
111
178
  * @remarks
112
179
  * Only applied, scoped findings contribute. Each named scope combines its own
@@ -124,109 +191,97 @@ import { Subject } from '@orkestrel/reason';
124
191
  * deriveScopeEligibilities([finding]) // { wind: 'ineligible' }
125
192
  * ```
126
193
  */
127
- export declare function deriveScopeEligibilities(findings: Finding[]): Readonly<Record<string, Eligibility>>;
194
+ export declare function deriveScopeEligibilities(findings: readonly Finding[]): Readonly<Record<string, Eligibility>>;
128
195
 
129
196
  /**
130
- * Describe a {@link Premise} comparison as a display-neutral verb phrase.
131
- *
132
- * @param comparison - The comparison to describe
133
- * @returns A display-neutral phrase
134
- *
135
- * @example
136
- * ```ts
137
- * import { describeComparison } from '@orkestrel/qualifier'
138
- *
139
- * describeComparison('above') // 'is more than'
140
- * ```
141
- */
142
- export declare function describeComparison(comparison: NonNullable<Premise['comparison']>): string;
143
-
144
- /**
145
- * Render one {@link Premise} into a display-neutral sentence.
197
+ * Describes each logical pass carrying no rulings.
146
198
  *
147
199
  * @remarks
148
- * The checked form renders only when `field` and `comparison` are BOTH
149
- * present, and `description` then goes unused; a premise missing either half
150
- * of the checked pair renders as described instead.
200
+ * A logical pass with no matching ruling can never contribute a finding, so its
201
+ * rules only ever influence later passes' reads — this is very likely an authoring
202
+ * oversight.
151
203
  *
152
- * @param entry - The premise to render
153
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
154
- * @returns A display-neutral sentence
204
+ * @param definition - The qualification definition to check
205
+ * @returns A fresh list of warning messages
155
206
  *
156
207
  * @example
157
208
  * ```ts
158
- * import { describePremise } from '@orkestrel/qualifier'
209
+ * import { createQualificationDefinition, describeEmptyLogicalPasses } from '@orkestrel/qualifier'
210
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
159
211
  *
160
- * describePremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })
161
- * // 'age is more than 18 → met'
212
+ * const gates = createLogicalDefinition('gates', 'Gates', [
213
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
214
+ * ])
215
+ * const definition = createQualificationDefinition('standard', 'Standard', [gates])
162
216
  *
163
- * describePremise({ description: 'Applicant is enrolled', met: true })
164
- * // 'Applicant is enrolled → met'
217
+ * describeEmptyLogicalPasses(definition) // ["Logical pass 'gates' has no rulings"]
165
218
  * ```
166
219
  */
167
- export declare function describePremise(entry: Premise, labels?: Readonly<Record<string, string>>): string;
220
+ export declare function describeEmptyLogicalPasses(definition: QualificationDefinition): readonly string[];
168
221
 
169
222
  /**
170
- * Render a structured or scalar expected value display-neutrally.
171
- *
172
- * @remarks
173
- * An array renders as its elements, each `String`-coerced, joined with `', '`.
174
- * A reason `Bounds`-shaped record (the `between` / `outside` expected value)
175
- * renders its present sides joined with the word `and`; an absent side is
176
- * omitted. A finite number and everything else fall back to plain `String()` —
177
- * a finite number is NOT grouped here (grouping is {@link interpolateMessage}'s
178
- * message-token-only behavior).
223
+ * Describes each ruling whose pass or rule does not exist or whose pass is not
224
+ * logical, and each pass id shadowing the reserved {@link QUALIFICATION_KEY}.
179
225
  *
180
- * @param value - The value to render
181
- * @returns A display-neutral rendering
226
+ * @param definition - The qualification definition to check
227
+ * @returns A fresh list of reference-error messages
182
228
  *
183
229
  * @example
184
230
  * ```ts
185
- * import { describeValue } from '@orkestrel/qualifier'
231
+ * import { createQualificationDefinition, createRuling, describeMissingReferences } from '@orkestrel/qualifier'
232
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
233
+ *
234
+ * const gates = createLogicalDefinition('gates', 'Gates', [
235
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
236
+ * ])
237
+ * const definition = createQualificationDefinition('standard', 'Standard', [gates], {
238
+ * rulings: [createRuling('license', 'gates', 'absent', 'restriction')],
239
+ * })
186
240
  *
187
- * describeValue([18, 25, 40]) // '18, 25, 40'
188
- * describeValue({ minimum: 18, maximum: 65 }) // '18 and 65'
189
- * describeValue(42) // '42'
241
+ * describeMissingReferences(definition)
242
+ * // ["Ruling 'license' references missing rule 'absent' in pass 'gates'"]
190
243
  * ```
191
244
  */
192
- export declare function describeValue(value: unknown): string;
193
-
194
- /** The eligibility impact of each {@link QualificationEffect}; `condition` remains eligible. */
195
- export declare const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>>;
196
-
197
- /** The eligibility outcome axis. */
198
- export declare type Eligibility = 'eligible' | 'ineligible' | 'referral';
199
-
200
- /** Eligibility severity order — most to least severe. */
201
- export declare const ELIGIBILITY_PRECEDENCE: readonly Eligibility[];
245
+ export declare function describeMissingReferences(definition: QualificationDefinition): readonly string[];
202
246
 
203
247
  /**
204
- * Find logical passes carrying no rulings.
248
+ * Describes each quantitative pass never read by a later pass.
205
249
  *
206
250
  * @remarks
207
- * A logical pass with no matching ruling can never contribute a finding, so its
208
- * rules only ever influence later passes' reads — this is very likely an authoring
209
- * oversight.
251
+ * A quantitative pass's projected value lives under `qualification.<id>` — if no
252
+ * later logical premise or quantitative factor field reads that key, the
253
+ * derivation is dead weight. Reads are collected per later pass: a logical pass's
254
+ * premise atoms (conclusion atoms are writes, not reads), and a quantitative
255
+ * pass's field/lookup/range factor sources plus factor checks.
210
256
  *
211
257
  * @param definition - The qualification definition to check
212
258
  * @returns A fresh list of warning messages
213
259
  *
214
260
  * @example
215
261
  * ```ts
216
- * import { findEmptyLogicalPasses, qualificationDefinition } from '@orkestrel/qualifier'
217
- * import { atom, logicalDefinition, rule } from '@orkestrel/reason'
262
+ * import { createQualificationDefinition, describeUnreadDerivations } from '@orkestrel/qualifier'
263
+ * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'
218
264
  *
219
- * const gates = logicalDefinition('gates', 'Gates', [
220
- * rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
265
+ * const cap = createQuantitativeDefinition('cap', 'TIV cap', [
266
+ * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),
221
267
  * ])
222
- * const definition = qualificationDefinition('standard', 'Standard', [gates])
268
+ * const definition = createQualificationDefinition('standard', 'Standard', [cap])
223
269
  *
224
- * findEmptyLogicalPasses(definition) // ["Logical pass 'gates' has no rulings"]
270
+ * describeUnreadDerivations(definition) // ["Quantitative pass 'cap' is never read by a later pass"]
225
271
  * ```
226
272
  */
227
- export declare function findEmptyLogicalPasses(definition: QualificationDefinition): readonly string[];
273
+ export declare function describeUnreadDerivations(definition: QualificationDefinition): readonly string[];
274
+
275
+ /** Maps each {@link QualificationEffect} to its eligibility impact; `condition` remains eligible. */
276
+ export declare const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>>;
277
+
278
+ /** Represents the eligibility outcome axis. */
279
+ export declare type Eligibility = 'eligible' | 'ineligible' | 'referral';
280
+
281
+ /** Lists the eligibility severities in order — most to least severe. */
282
+ export declare const ELIGIBILITY_PRECEDENCE: readonly Eligibility[];
228
283
 
229
- /** One resolved ruling. */
284
+ /** Represents one resolved ruling. */
230
285
  export declare interface Finding {
231
286
  readonly id: string;
232
287
  readonly pass: string;
@@ -239,32 +294,7 @@ import { Subject } from '@orkestrel/reason';
239
294
  }
240
295
 
241
296
  /**
242
- * Find rulings whose pass or rule does not exist, whose pass is not logical, or
243
- * a pass id shadowing the reserved {@link QUALIFICATION_KEY}.
244
- *
245
- * @param definition - The qualification definition to check
246
- * @returns A fresh list of reference-error messages
247
- *
248
- * @example
249
- * ```ts
250
- * import { findMissingReferences, qualificationDefinition, rulingDefinition } from '@orkestrel/qualifier'
251
- * import { atom, logicalDefinition, rule } from '@orkestrel/reason'
252
- *
253
- * const gates = logicalDefinition('gates', 'Gates', [
254
- * rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
255
- * ])
256
- * const definition = qualificationDefinition('standard', 'Standard', [gates], {
257
- * rulings: [rulingDefinition('license', 'gates', 'absent', 'restriction')],
258
- * })
259
- *
260
- * findMissingReferences(definition)
261
- * // ["Ruling 'license' references missing rule 'absent' in pass 'gates'"]
262
- * ```
263
- */
264
- export declare function findMissingReferences(definition: QualificationDefinition): readonly string[];
265
-
266
- /**
267
- * Locate an authored {@link Rule} by id.
297
+ * Locates an authored {@link Rule} by id.
268
298
  *
269
299
  * @param definition - The logical definition to search
270
300
  * @param id - The rule id
@@ -273,10 +303,10 @@ import { Subject } from '@orkestrel/reason';
273
303
  * @example
274
304
  * ```ts
275
305
  * import { findRule } from '@orkestrel/qualifier'
276
- * import { atom, logicalDefinition, rule } from '@orkestrel/reason'
306
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
277
307
  *
278
- * const gates = logicalDefinition('gates', 'Gates', [
279
- * rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
308
+ * const gates = createLogicalDefinition('gates', 'Gates', [
309
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
280
310
  * ])
281
311
  *
282
312
  * findRule(gates, 'licensed')?.id // 'licensed'
@@ -285,38 +315,10 @@ import { Subject } from '@orkestrel/reason';
285
315
  export declare function findRule(definition: LogicalDefinition, id: string): Rule | undefined;
286
316
 
287
317
  /**
288
- * Find quantitative passes never read by a later pass.
289
- *
290
- * @remarks
291
- * A quantitative pass's projected value lives under `qualification.<id>` — if no
292
- * later logical premise or quantitative factor field reads that key, the
293
- * derivation is dead weight. Reads are collected per later pass: a logical pass's
294
- * premise atoms (conclusion atoms are writes, not reads), and a quantitative
295
- * pass's field/lookup/range factor sources plus factor checks.
296
- *
297
- * @param definition - The qualification definition to check
298
- * @returns A fresh list of warning messages
299
- *
300
- * @example
301
- * ```ts
302
- * import { findUnreadDerivations, qualificationDefinition } from '@orkestrel/qualifier'
303
- * import { factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
304
- *
305
- * const cap = quantitativeDefinition('cap', 'TIV cap', [
306
- * factorGroup('limit', 'sum', [staticFactor('base', 500_000)]),
307
- * ])
308
- * const definition = qualificationDefinition('standard', 'Standard', [cap])
309
- *
310
- * findUnreadDerivations(definition) // ["Quantitative pass 'cap' is never read by a later pass"]
311
- * ```
312
- */
313
- export declare function findUnreadDerivations(definition: QualificationDefinition): readonly string[];
314
-
315
- /**
316
- * Determine whether a subject already owns the reserved {@link QUALIFICATION_KEY}.
318
+ * Determines whether a subject already owns the reserved {@link QUALIFICATION_KEY}.
317
319
  *
318
320
  * @param subject - The subject to check
319
- * @returns `true` when the subject owns the reserved key
321
+ * @returns True if the subject owns the reserved key; false otherwise
320
322
  *
321
323
  * @example
322
324
  * ```ts
@@ -328,7 +330,7 @@ import { Subject } from '@orkestrel/reason';
328
330
  export declare function hasReservedKey(subject: Subject): boolean;
329
331
 
330
332
  /**
331
- * Interpolate `{{dotted.path}}` tokens in a message template against a subject.
333
+ * Interpolates `{{dotted.path}}` tokens in a message template against a subject.
332
334
  *
333
335
  * @remarks
334
336
  * Each token is split on `.` into a `FieldPath` array and resolved with
@@ -352,46 +354,52 @@ import { Subject } from '@orkestrel/reason';
352
354
  export declare function interpolateMessage(template: string, subject: Readonly<Record<string, unknown>>): string;
353
355
 
354
356
  /**
355
- * Determine whether a value is an open result-side {@link Derivation}.
357
+ * Determines whether a value is an open result-side {@link Derivation}.
356
358
  *
357
359
  * @remarks
358
360
  * Unknown members are admitted. `value` follows the published `number` type, including `NaN` and
359
361
  * infinities.
360
362
  *
361
363
  * @param value - The value to test
362
- * @returns `true` when every published derivation member is valid
364
+ * @returns True if every published derivation member is valid; false otherwise
363
365
  */
364
366
  export declare function isDerivation(value: unknown): value is Derivation;
365
367
 
366
- /** Determine whether a value is an {@link Eligibility} literal. */
368
+ /**
369
+ * Determines whether a value is an {@link Eligibility} literal.
370
+ *
371
+ * @param value - The value to test
372
+ * @returns True if `value` is one of the eligibility literals; false otherwise
373
+ */
367
374
  export declare const isEligibility: Guard<"eligible" | "ineligible" | "referral">;
368
375
 
369
376
  /**
370
- * Determine whether a value is an open string-keyed record of {@link Eligibility} values.
377
+ * Determines whether a value is an open string-keyed record of {@link Eligibility} values.
371
378
  *
372
379
  * @remarks
373
380
  * Every own string-named property is checked, including non-enumerable properties. Inherited and
374
381
  * symbol-named members are left unchecked because they are outside the record this guard certifies.
375
382
  *
376
383
  * @param value - The value to test
377
- * @returns `true` when `value` is a non-array object whose own string-named values are eligibilities
384
+ * @returns True if `value` is a non-array object whose own string-named values are
385
+ * eligibilities; false otherwise
378
386
  */
379
387
  export declare function isEligibilityRecord(value: unknown): value is Readonly<Record<string, Eligibility>>;
380
388
 
381
389
  /**
382
- * Determine whether a value is an open result-side {@link Finding}.
390
+ * Determines whether a value is an open result-side {@link Finding}.
383
391
  *
384
392
  * @remarks
385
393
  * Unknown members are admitted. Optional `scope` and `message` members may be absent or
386
394
  * `undefined`; a present defined value must be a string.
387
395
  *
388
396
  * @param value - The value to test
389
- * @returns `true` when every published finding member is valid
397
+ * @returns True if every published finding member is valid; false otherwise
390
398
  */
391
399
  export declare function isFinding(value: unknown): value is Finding;
392
400
 
393
401
  /**
394
- * Determine whether a value is an open result-side {@link Premise}.
402
+ * Determines whether a value is an open result-side {@link Premise}.
395
403
  *
396
404
  * @remarks
397
405
  * `expected` is unchecked and may be absent or contain any value because its published type is
@@ -399,91 +407,69 @@ import { Subject } from '@orkestrel/reason';
399
407
  * Unknown members are admitted.
400
408
  *
401
409
  * @param value - The value to test
402
- * @returns `true` when every checked premise member follows its published type
410
+ * @returns True if every checked premise member follows its published type; false otherwise
403
411
  */
404
412
  export declare function isPremise(value: unknown): value is Premise;
405
413
 
406
- /** Determine whether a value is an exact {@link QualificationDefinition} record. */
414
+ /**
415
+ * Determines whether a value is an exact {@link QualificationDefinition} record.
416
+ *
417
+ * @param value - The value to test
418
+ * @returns True if `value` carries every definition member and no unknown key; false otherwise
419
+ */
407
420
  export declare function isQualificationDefinition(value: unknown): value is QualificationDefinition;
408
421
 
409
- /** Determine whether a value is a {@link QualificationEffect} literal. */
422
+ /**
423
+ * Determines whether a value is a {@link QualificationEffect} literal.
424
+ *
425
+ * @param value - The value to test
426
+ * @returns True if `value` is one of the effect literals; false otherwise
427
+ */
410
428
  export declare const isQualificationEffect: Guard<"referral" | "restriction" | "condition">;
411
429
 
412
- /** Determine whether a value is a {@link QualificationPass} (a quantitative or logical definition). */
430
+ /**
431
+ * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
432
+ *
433
+ * @param value - The value to test
434
+ * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise
435
+ */
413
436
  export declare function isQualificationPass(value: unknown): value is QualificationPass;
414
437
 
415
438
  /**
416
- * Determine whether a value is an open {@link QualificationResult} returned by a qualifier.
439
+ * Determines whether a value is an open {@link QualificationResult} returned by a qualifier.
417
440
  *
418
441
  * @remarks
419
442
  * This guard is result-postured for values returned through a borrowed qualifier interface. It
420
443
  * admits unknown members and class instances while checking the complete published result closure.
421
444
  *
422
445
  * @param value - The value to test
423
- * @returns `true` when every published qualification-result member is valid
446
+ * @returns True if every published qualification-result member is valid; false otherwise
424
447
  */
425
448
  export declare function isQualificationResult(value: unknown): value is QualificationResult;
426
449
 
427
450
  /**
428
- * Determine whether a value is an open {@link QualificationValidationResult} returned by a qualifier.
429
- *
430
- * @remarks
431
- * `QualificationValidationResult` is an alias of reasons' validation-result type, so this IS
432
- * reasons' published open result guard by delegation — the alias tracks reasons' shape
433
- * automatically, and a hand-written member list here would silently stop checking a member a
434
- * later reason release adds.
451
+ * Narrows a caught value to a {@link QualifierError}.
435
452
  *
436
- * @param value - The value to test
437
- * @returns `true` when every published qualification-validation-result member is valid
453
+ * @param value - The caught value to test
454
+ * @returns True if `value` is a {@link QualifierError} instance; false otherwise
438
455
  */
439
- export declare const isQualificationValidationResult: Guard<QualificationValidationResult>;
440
-
441
- /** Narrow a caught value to a {@link QualifierError}. */
442
456
  export declare function isQualifierError(value: unknown): value is QualifierError;
443
457
 
444
- /** Determine whether a value is an exact {@link Ruling} record. */
445
- export declare function isRuling(value: unknown): value is Ruling;
446
-
447
458
  /**
448
- * Build rich premises for one fired {@link Rule} by walking its premise atoms
449
- * and re-evaluating each against the working subject.
459
+ * Determines whether a value is an exact {@link Ruling} record.
450
460
  *
451
- * @remarks
452
- * A reason rule result carries only booleans, so this is the qualifier's own
453
- * premise-audit projection: each authored premise expression is flattened to its
454
- * atom leaves via `extractAtoms`, and each leaf's `Check` is re-evaluated through
455
- * the injected `evaluator`. A membership check (`any` / `none`) over an EMPTY
456
- * array value is content-free and is skipped.
457
- *
458
- * @param rule - The authored rule
459
- * @param working - The working subject to evaluate against
460
- * @param evaluator - The shared reason check evaluator
461
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
462
- * @returns A fresh, ordered list of rich premises
463
- *
464
- * @example
465
- * ```ts
466
- * import { logicalPremises } from '@orkestrel/qualifier'
467
- * import { atom, createEvaluator, logicalDefinition, rule } from '@orkestrel/reason'
468
- *
469
- * const gates = logicalDefinition('gates', 'Gates', [
470
- * rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
471
- * ])
472
- * const evaluator = createEvaluator()
473
- * const premises = logicalPremises(gates.rules[0], { licensed: false }, evaluator)
474
- *
475
- * premises[0]?.met // true
476
- * ```
461
+ * @param value - The value to test
462
+ * @returns True if `value` carries every ruling member and no unknown key; false otherwise
477
463
  */
478
- export declare function logicalPremises(rule: Rule, working: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): readonly Premise[];
464
+ export declare function isRuling(value: unknown): value is Ruling;
479
465
 
480
466
  /**
481
- * Map an engine throw caught while running one pass to a typed {@link QualifierError}.
467
+ * Maps an engine throw caught while running one pass to a typed {@link QualifierError}.
482
468
  *
483
469
  * @remarks
484
470
  * A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the
485
471
  * pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;
486
- * every other `ReasonError` code (e.g. `MISSING`) and every non-`ReasonError` throw
472
+ * every other `ReasonError` code (for example `MISSING`) and every non-`ReasonError` throw
487
473
  * (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw
488
474
  * is preserved as `context.cause`.
489
475
  *
@@ -502,7 +488,7 @@ import { Subject } from '@orkestrel/reason';
502
488
  export declare function mapEngineError(error: unknown, pass: string): QualifierError;
503
489
 
504
490
  /**
505
- * Copy-on-write merge one pass projection into the context.
491
+ * Merges one pass projection into the context, copy-on-write.
506
492
  *
507
493
  * @param context - The current projection context
508
494
  * @param id - The pass id to project under
@@ -520,7 +506,7 @@ import { Subject } from '@orkestrel/reason';
520
506
  export declare function mergeQualificationContext(context: QualificationContext, id: string, projection: QualificationProjection): QualificationContext;
521
507
 
522
508
  /**
523
- * Display-neutral evidence for one condition, in one of two authoring modes.
509
+ * Represents display-neutral evidence for one condition, in one of two authoring modes.
524
510
  * A CHECKED premise carries `field` and `comparison`; a DESCRIBED premise
525
511
  * carries neither and renders from `description` instead. The checked form
526
512
  * renders only when `field` and `comparison` are BOTH present, and
@@ -538,34 +524,13 @@ import { Subject } from '@orkestrel/reason';
538
524
  readonly met?: boolean;
539
525
  }
540
526
 
541
- /**
542
- * Build a {@link Premise} from an authored {@link Check} and its evaluated
543
- * `CheckResult`.
544
- *
545
- * @param check - The authored check
546
- * @param result - The evaluated check result carrying the resolved `actual` and `met`
547
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
548
- * @returns A fresh premise
549
- *
550
- * @example
551
- * ```ts
552
- * import { premiseCheck } from '@orkestrel/qualifier'
553
- * import { check } from '@orkestrel/reason'
554
- *
555
- * const authored = check('age', 'above', 18)
556
- * premiseCheck(authored, { field: 'age', actual: 25, met: true })
557
- * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }
558
- * ```
559
- */
560
- export declare function premiseCheck(check: Check, result: CheckResult, labels?: Readonly<Record<string, string>>): Premise;
561
-
562
- /** The reserved internal projection namespace a pass's working projection is written under. */
527
+ /** Names the reserved internal projection namespace a pass's working projection is written under. */
563
528
  export declare const QUALIFICATION_KEY = "qualification";
564
529
 
565
- /** The internal projection record stored under `QUALIFICATION_KEY`. */
530
+ /** Represents the internal projection record stored under `QUALIFICATION_KEY`. */
566
531
  export declare type QualificationContext = Readonly<Record<string, QualificationProjection>>;
567
532
 
568
- /** A pure authored qualification definition. */
533
+ /** Represents a pure authored qualification definition. */
569
534
  export declare interface QualificationDefinition {
570
535
  readonly id: string;
571
536
  readonly name: string;
@@ -575,46 +540,23 @@ import { Subject } from '@orkestrel/reason';
575
540
  readonly metadata?: Readonly<Record<string, JSONValue>>;
576
541
  }
577
542
 
578
- /**
579
- * Build a {@link QualificationDefinition}.
580
- *
581
- * @remarks
582
- * Returns a fresh top-level definition, omitting absent optional keys;
583
- * `passes` and `rulings` arrays are copied, and record `metadata` is
584
- * shallow-copied so nested values are not deep-cloned.
585
- *
586
- * @param id - The definition id
587
- * @param name - The display name
588
- * @param passes - The ordered qualification passes
589
- * @param input - Optional description, rulings, and metadata
590
- * @returns A fresh qualification definition
591
- *
592
- * @example
593
- * ```ts
594
- * import { qualificationDefinition } from '@orkestrel/qualifier'
595
- *
596
- * qualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })
597
- * ```
598
- */
599
- export declare function qualificationDefinition(id: string, name: string, passes: QualificationPass[], input?: QualificationInput): QualificationDefinition;
600
-
601
- /** An authored ruling's eligibility impact. */
543
+ /** Represents an authored ruling's eligibility impact. */
602
544
  export declare type QualificationEffect = 'restriction' | 'referral' | 'condition';
603
545
 
604
- /** Optional fields accepted by `qualificationDefinition`. */
546
+ /** Carries the optional fields `createQualificationDefinition` accepts. */
605
547
  export declare interface QualificationInput {
606
548
  readonly description?: string;
607
549
  readonly rulings?: readonly Ruling[];
608
550
  readonly metadata?: Readonly<Record<string, JSONValue>>;
609
551
  }
610
552
 
611
- /** One ordered derivation or rule pass. */
553
+ /** Represents one ordered derivation or rule pass. */
612
554
  export declare type QualificationPass = QuantitativeDefinition | LogicalDefinition;
613
555
 
614
- /** One pass's internal working projection. */
556
+ /** Represents one pass's internal working projection. */
615
557
  export declare type QualificationProjection = number | boolean | Readonly<Record<string, unknown>>;
616
558
 
617
- /** One subject's complete qualification outcome. */
559
+ /** Represents one subject's complete qualification outcome. */
618
560
  export declare interface QualificationResult {
619
561
  readonly id: string;
620
562
  readonly name: string;
@@ -628,7 +570,7 @@ import { Subject } from '@orkestrel/reason';
628
570
  }
629
571
 
630
572
  /**
631
- * Wrap a {@link QualificationContext} under {@link QUALIFICATION_KEY}.
573
+ * Wraps a {@link QualificationContext} under {@link QUALIFICATION_KEY}.
632
574
  *
633
575
  * @param context - The accumulated projection context
634
576
  * @returns A record carrying the context under the reserved key
@@ -642,12 +584,8 @@ import { Subject } from '@orkestrel/reason';
642
584
  */
643
585
  export declare function qualificationToRecord(context: QualificationContext): Readonly<Record<string, QualificationContext>>;
644
586
 
645
- /** Semantic definition validation. */
646
- export declare type QualificationValidationResult = ReasonValidationResult;
647
-
648
587
  /**
649
- * A qualifier — runs ordered passes over one reason engine and returns
650
- * eligibility.
588
+ * Runs ordered passes over one reason engine and returns eligibility.
651
589
  *
652
590
  * @remarks
653
591
  * The engine is OWNED when self-created (destroyed on `destroy()`) and borrowed
@@ -658,177 +596,455 @@ import { Subject } from '@orkestrel/reason';
658
596
  export declare class Qualifier implements QualifierInterface {
659
597
  #private;
660
598
  constructor(options?: QualifierOptions);
599
+ /**
600
+ * Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and
601
+ * `destroy`.
602
+ *
603
+ * @returns The emitter this qualifier owns
604
+ *
605
+ * @example
606
+ * ```ts
607
+ * import { Qualifier } from '@orkestrel/qualifier'
608
+ *
609
+ * const qualifier = new Qualifier()
610
+ * qualifier.emitter.on('qualify', (result) => console.log(result.eligibility))
611
+ * qualifier.destroy()
612
+ * ```
613
+ */
661
614
  get emitter(): EmitterInterface<QualifierEventMap>;
662
- qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
663
- validate(definition: QualificationDefinition): QualificationValidationResult;
664
- destroy(): void;
665
- }
615
+ /**
616
+ * Qualifies one subject against one authored definition.
617
+ *
618
+ * @remarks
619
+ * Semantic validation runs first when the `validate` option is on, so an invalid
620
+ * definition throws before any pass runs. Passes run in authored order over a
621
+ * copy-on-write working subject, and the caller's `subject` is never mutated. An
622
+ * unscoped restriction stops the remaining passes because no later finding can be
623
+ * more severe.
624
+ *
625
+ * @param subject - The record to qualify
626
+ * @param definition - The authored qualification definition
627
+ * @returns A fresh, frozen qualification result
628
+ * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the
629
+ * definition fails semantic validation, or when the engine rejects a pass as an invalid
630
+ * definition; `'MISMATCH'` when the subject is not a record or already carries the
631
+ * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine
632
+ * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * import { createQualificationDefinition, createRuling, Qualifier } from '@orkestrel/qualifier'
637
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
638
+ *
639
+ * const gates = createLogicalDefinition('gates', 'Eligibility gates', [
640
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
641
+ * ])
642
+ * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {
643
+ * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],
644
+ * })
645
+ *
646
+ * const qualifier = new Qualifier()
647
+ * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'
648
+ * qualifier.destroy()
649
+ * ```
650
+ */
651
+ qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
652
+ /**
653
+ * Validates one authored definition semantically, without running it.
654
+ *
655
+ * @remarks
656
+ * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:
657
+ * non-empty id and name, valid and uniquely identified passes and rulings, every
658
+ * ruling reference resolving to a logical pass and one of its rules, and no pass id
659
+ * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no
660
+ * ruling, and a derivation no later pass reads are warnings rather than errors.
661
+ *
662
+ * @param definition - The authored qualification definition
663
+ * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`
664
+ * @throws {@link QualifierError} `'DESTROYED'` after `destroy`
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * import { createQualificationDefinition, Qualifier } from '@orkestrel/qualifier'
669
+ *
670
+ * const qualifier = new Qualifier()
671
+ * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))
672
+ * // { valid: true, errors: [], warnings: ['Definition has no passes'] }
673
+ * qualifier.destroy()
674
+ * ```
675
+ */
676
+ validate(definition: QualificationDefinition): ReasonValidationResult;
677
+ /**
678
+ * Destroys this qualifier, idempotently.
679
+ *
680
+ * @remarks
681
+ * An engine this qualifier created is destroyed; an injected engine stays
682
+ * caller-owned and is left alone. The `destroy` event fires before the emitter
683
+ * itself is destroyed, and a later call returns without re-emitting.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * import { Qualifier } from '@orkestrel/qualifier'
688
+ *
689
+ * const qualifier = new Qualifier()
690
+ * qualifier.destroy()
691
+ * qualifier.destroy() // idempotent
692
+ * ```
693
+ */
694
+ destroy(): void;
695
+ }
666
696
 
667
- /**
668
- * A coded programmer error thrown by the qualifier layer.
669
- *
670
- * @remarks
671
- * `DEFINITION` — a qualification definition failed semantic validation.
672
- * `MISMATCH` — a subject is not a record or already carries the reserved
673
- * `qualification` key. `DESTROYED` — use of a destroyed qualifier.
674
- * `ENGINE` — the underlying reason engine threw while running a pass (the
675
- * original throw is preserved as `context.cause`).
676
- */
677
- export declare class QualifierError extends Error {
678
- readonly code: QualifierErrorCode;
679
- readonly context: unknown;
680
- constructor(code: QualifierErrorCode, message: string, context?: unknown);
681
- }
697
+ /**
698
+ * Represents a coded programmer error thrown by the qualifier layer.
699
+ *
700
+ * @remarks
701
+ * `DEFINITION` — a qualification definition failed semantic validation; when
702
+ * `qualify`'s own validation raises it, `context.definition` names the
703
+ * definition, and when an engine throw maps to it, `context.pass` names the pass
704
+ * and `context.cause` preserves the original throw. `MISMATCH` — a subject is
705
+ * not a record or already carries the reserved `qualification` key.
706
+ * `DESTROYED` — use of a destroyed qualifier; an engine-mapped one carries
707
+ * `context.pass` and `context.cause`. `ENGINE` — the underlying reason engine
708
+ * threw while running a pass; `context.pass` names the pass and `context.cause`
709
+ * preserves the original throw. `context` is absent for a throw that carries no
710
+ * payload.
711
+ */
712
+ export declare class QualifierError extends Error {
713
+ readonly code: QualifierErrorCode;
714
+ readonly context: QualifierErrorContext | undefined;
715
+ constructor(code: QualifierErrorCode, message: string, context?: QualifierErrorContext);
716
+ }
682
717
 
683
- /** A coded {@link QualifierError} programmer-error code. */
684
- export declare type QualifierErrorCode = 'DEFINITION' | 'MISMATCH' | 'DESTROYED' | 'ENGINE';
685
-
686
- /** The push observation surface of a {@link QualifierInterface} (AGENTS §13). */
687
- export declare type QualifierEventMap = {
688
- readonly derive: readonly [derivation: Derivation];
689
- readonly finding: readonly [finding: Finding];
690
- readonly qualify: readonly [result: QualificationResult];
691
- readonly destroy: readonly [];
692
- };
693
-
694
- /** One qualifier — owns or borrows one reason engine and returns eligibility. */
695
- export declare interface QualifierInterface {
696
- readonly emitter: EmitterInterface<QualifierEventMap>;
697
- qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
698
- validate(definition: QualificationDefinition): QualificationValidationResult;
699
- destroy(): void;
700
- }
718
+ /** Represents a coded {@link QualifierError} programmer-error code. */
719
+ export declare type QualifierErrorCode = 'DEFINITION' | 'MISMATCH' | 'DESTROYED' | 'ENGINE';
701
720
 
702
- /** Options for `createQualifier` / the `Qualifier` constructor. */
703
- export declare interface QualifierOptions {
704
- readonly engine?: ReasonInterface;
705
- readonly validate?: boolean;
706
- readonly labels?: Readonly<Record<string, string>>;
707
- readonly on?: EmitterHooks<QualifierEventMap>;
708
- readonly error?: EmitterErrorHandler;
709
- }
721
+ /**
722
+ * Represents the structured payload a {@link QualifierError} carries.
723
+ *
724
+ * @remarks
725
+ * `pass` names the pass that was running when the engine threw. `definition` names the
726
+ * qualification definition that failed semantic validation. `cause` is the original throw
727
+ * an engine failure wrapped, and stays `unknown` because a thrown value can be anything.
728
+ * Every member is absent rather than `undefined` when the throw does not carry it.
729
+ */
730
+ export declare interface QualifierErrorContext {
731
+ readonly pass?: string;
732
+ readonly definition?: string;
733
+ readonly cause?: unknown;
734
+ }
710
735
 
711
- /**
712
- * Project a quantitative result into a {@link Derivation} audit record.
713
- *
714
- * @param id - The pass id
715
- * @param result - The evaluated quantitative result
716
- * @returns A fresh derivation
717
- *
718
- * @example
719
- * ```ts
720
- * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'
721
- *
722
- * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }
723
- * quantitativeResultToDerivation('cap', result)
724
- * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }
725
- * ```
726
- */
727
- export declare function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation;
736
+ /** Represents the push observation surface of a {@link QualifierInterface}. */
737
+ export declare type QualifierEventMap = {
738
+ readonly derive: readonly [derivation: Derivation];
739
+ readonly finding: readonly [finding: Finding];
740
+ readonly qualify: readonly [result: QualificationResult];
741
+ readonly destroy: readonly [];
742
+ };
728
743
 
729
- /**
730
- * Project one reason result into the internal qualification namespace.
731
- *
732
- * @remarks
733
- * A quantitative result contributes its numeric `value`. A logical result
734
- * contributes a record of its `conclusion` plus every applied rule's asserted
735
- * conclusion fields (`extractConclusions`). A mismatched pairing contributes
736
- * `false`. The projection is pure — it never mutates the subject or the result.
737
- *
738
- * @param definition - The pass definition that produced the result
739
- * @param result - The evaluated reason result
740
- * @returns The pass's working projection
741
- *
742
- * @example
743
- * ```ts
744
- * import { reasonResultToProjection } from '@orkestrel/qualifier'
745
- * import { factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
746
- *
747
- * const cap = quantitativeDefinition('cap', 'TIV cap', [
748
- * factorGroup('limit', 'sum', [staticFactor('base', 500_000)]),
749
- * ])
750
- * const result = {
751
- * reasoning: 'quantitative' as const,
752
- * value: 500_000,
753
- * groups: [],
754
- * count: 1,
755
- * success: true,
756
- * trace: [],
757
- * errors: [],
758
- * }
759
- * reasonResultToProjection(cap, result) // 500000
760
- * ```
761
- */
762
- export declare function reasonResultToProjection(definition: QualificationPass, result: ReasonResult): QualificationProjection;
744
+ /** Owns or borrows one reason engine and returns eligibility. */
745
+ export declare interface QualifierInterface {
746
+ /** Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and `destroy`. */
747
+ readonly emitter: EmitterInterface<QualifierEventMap>;
748
+ /**
749
+ * Qualifies one subject against one authored definition.
750
+ *
751
+ * @remarks
752
+ * Semantic validation runs first when the `validate` option is on, so an invalid
753
+ * definition throws before any pass runs. Passes run in authored order and the
754
+ * caller's `subject` is never mutated: the working projection under
755
+ * `QUALIFICATION_KEY` is built copy-on-write and discarded after the call.
756
+ *
757
+ * @param subject - The record to qualify
758
+ * @param definition - The authored qualification definition
759
+ * @returns A fresh, frozen qualification result
760
+ * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the
761
+ * definition fails semantic validation, or when the engine rejects a pass as an invalid
762
+ * definition; `'MISMATCH'` when the subject is not a record or already carries the
763
+ * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine
764
+ * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'
769
+ * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
770
+ *
771
+ * const gates = createLogicalDefinition('gates', 'Eligibility gates', [
772
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
773
+ * ])
774
+ * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {
775
+ * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],
776
+ * })
777
+ *
778
+ * const qualifier = createQualifier()
779
+ * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'
780
+ * qualifier.destroy()
781
+ * ```
782
+ */
783
+ qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
784
+ /**
785
+ * Validates one authored definition semantically, without running it.
786
+ *
787
+ * @remarks
788
+ * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:
789
+ * non-empty id and name, valid and uniquely identified passes and rulings, every
790
+ * ruling reference resolving to a logical pass and one of its rules, and no pass id
791
+ * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no
792
+ * ruling, and a derivation no later pass reads are warnings rather than errors.
793
+ *
794
+ * @param definition - The authored qualification definition
795
+ * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`
796
+ * @throws {@link QualifierError} `'DESTROYED'` after `destroy`
797
+ *
798
+ * @example
799
+ * ```ts
800
+ * import { createQualificationDefinition, createQualifier } from '@orkestrel/qualifier'
801
+ *
802
+ * const qualifier = createQualifier()
803
+ * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))
804
+ * // { valid: true, errors: [], warnings: ['Definition has no passes'] }
805
+ * qualifier.destroy()
806
+ * ```
807
+ */
808
+ validate(definition: QualificationDefinition): ReasonValidationResult;
809
+ /**
810
+ * Destroys this qualifier, idempotently.
811
+ *
812
+ * @remarks
813
+ * An owned engine is destroyed; an injected engine stays caller-owned and is left
814
+ * alone. The `destroy` event fires before the emitter itself is destroyed, and a
815
+ * later call returns without re-emitting.
816
+ *
817
+ * @example
818
+ * ```ts
819
+ * import { createQualifier } from '@orkestrel/qualifier'
820
+ *
821
+ * const qualifier = createQualifier()
822
+ * qualifier.destroy()
823
+ * qualifier.destroy() // idempotent
824
+ * ```
825
+ */
826
+ destroy(): void;
827
+ }
763
828
 
764
- /** An authored consequence for one rule in one logical pass. */
765
- export declare interface Ruling {
766
- readonly id: string;
767
- readonly pass: string;
768
- readonly rule: string;
769
- readonly effect: QualificationEffect;
770
- readonly scope?: string;
771
- readonly message?: string;
772
- }
829
+ /**
830
+ * Carries the options for `createQualifier` / the `Qualifier` constructor.
831
+ *
832
+ * @remarks
833
+ * `engine` injects a reason engine that stays caller-owned and is never destroyed; an
834
+ * absent `engine` makes the qualifier create and own one. `validate` runs semantic
835
+ * validation before every qualification. Default: `true`. `labels` overrides the display
836
+ * name of a premise field, keyed by the dot-joined field path `formatField` produces, and
837
+ * takes precedence over a premise's own `label`. `on` carries the initial emitter hooks,
838
+ * and `error` handles a listener throw.
839
+ */
840
+ export declare interface QualifierOptions {
841
+ readonly engine?: ReasonInterface;
842
+ readonly validate?: boolean;
843
+ readonly labels?: Readonly<Record<string, string>>;
844
+ readonly on?: EmitterHooks<QualifierEventMap>;
845
+ readonly error?: EmitterErrorHandler;
846
+ }
773
847
 
774
- /**
775
- * Build a {@link Ruling} — one authored consequence for one rule in one pass.
776
- *
777
- * @param id - The ruling id
778
- * @param pass - The logical pass id the rule lives in
779
- * @param rule - The rule id whose firing this ruling reacts to
780
- * @param effect - The eligibility impact when the rule fires
781
- * @param input - Optional selection scope and message template
782
- * @returns A fresh ruling
783
- *
784
- * @example
785
- * ```ts
786
- * import { rulingDefinition } from '@orkestrel/qualifier'
787
- *
788
- * rulingDefinition('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })
789
- * ```
790
- */
791
- export declare function rulingDefinition(id: string, pass: string, rule: string, effect: QualificationEffect, input?: RulingInput): Ruling;
848
+ /**
849
+ * Projects a quantitative result into a {@link Derivation} audit record.
850
+ *
851
+ * @param id - The pass id
852
+ * @param result - The evaluated quantitative result
853
+ * @returns A fresh derivation
854
+ *
855
+ * @example
856
+ * ```ts
857
+ * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'
858
+ *
859
+ * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }
860
+ * quantitativeResultToDerivation('cap', result)
861
+ * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }
862
+ * ```
863
+ */
864
+ export declare function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation;
792
865
 
793
- /** Optional fields accepted by `rulingDefinition`. */
794
- export declare interface RulingInput {
795
- readonly scope?: string;
796
- readonly message?: string;
797
- }
866
+ /**
867
+ * Projects one reason result into the internal qualification namespace.
868
+ *
869
+ * @remarks
870
+ * A quantitative result contributes its numeric `value`. A logical result
871
+ * contributes a record of its `conclusion` plus every applied rule's asserted
872
+ * conclusion fields (`extractConclusions`). A mismatched pairing contributes
873
+ * `false`. The projection is pure — it never mutates the subject or the result.
874
+ *
875
+ * @param definition - The pass definition that produced the result
876
+ * @param result - The evaluated reason result
877
+ * @returns The pass's working projection
878
+ *
879
+ * @example
880
+ * ```ts
881
+ * import { reasonResultToProjection } from '@orkestrel/qualifier'
882
+ * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'
883
+ *
884
+ * const cap = createQuantitativeDefinition('cap', 'TIV cap', [
885
+ * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),
886
+ * ])
887
+ * const result = {
888
+ * reasoning: 'quantitative' as const,
889
+ * value: 500_000,
890
+ * groups: [],
891
+ * count: 1,
892
+ * success: true,
893
+ * trace: [],
894
+ * errors: [],
895
+ * }
896
+ * reasonResultToProjection(cap, result) // 500000
897
+ * ```
898
+ */
899
+ export declare function reasonResultToProjection(definition: QualificationPass, result: ReasonResult): QualificationProjection;
798
900
 
799
- /**
800
- * Join a ruling, its logical rule result, the pass, the pre-projection subject,
801
- * and an evaluator into a {@link Finding}.
802
- *
803
- * @remarks
804
- * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated
805
- * against the SAME subject snapshot the pass evaluated, so a conclusion the pass
806
- * derived can never satisfy its own reported premise. The message interpolates
807
- * against that snapshot.
808
- *
809
- * @param ruling - The authored ruling
810
- * @param pass - The pass the ruling's rule lives in
811
- * @param result - The pass's evaluated logical result
812
- * @param subject - The pre-projection subject snapshot the pass evaluated
813
- * @param evaluator - The shared reason check evaluator
814
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
815
- * @returns A fresh finding
816
- *
817
- * @example
818
- * ```ts
819
- * import { rulingToFinding } from '@orkestrel/qualifier'
820
- * import { atom, createEvaluator, logicalDefinition, rule } from '@orkestrel/reason'
821
- *
822
- * const gates = logicalDefinition('gates', 'Gates', [
823
- * rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
824
- * ])
825
- * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }
826
- * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true], conclusion: true }], count: 1, success: true, trace: [], errors: [] }
827
- * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())
828
- *
829
- * finding.applied // true
830
- * ```
831
- */
832
- export declare function rulingToFinding(ruling: Ruling, pass: QualificationPass, result: LogicalResult, subject: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): Finding;
901
+ /**
902
+ * Renders a {@link Premise} comparison as a display-neutral verb phrase.
903
+ *
904
+ * @param comparison - The comparison to describe
905
+ * @returns A display-neutral phrase
906
+ *
907
+ * @example
908
+ * ```ts
909
+ * import { renderComparison } from '@orkestrel/qualifier'
910
+ *
911
+ * renderComparison('above') // 'is more than'
912
+ * ```
913
+ */
914
+ export declare function renderComparison(comparison: NonNullable<Premise['comparison']>): string;
915
+
916
+ /**
917
+ * Renders one {@link Premise} into a display-neutral sentence.
918
+ *
919
+ * @remarks
920
+ * The checked form renders only when `field` and `comparison` are BOTH
921
+ * present, and `description` then goes unused; a premise missing either half
922
+ * of the checked pair renders as described instead.
923
+ *
924
+ * @param entry - The premise to render
925
+ * @param labels - Optional field-to-label overrides, keyed by dot-joined field
926
+ * @returns A display-neutral sentence
927
+ *
928
+ * @example
929
+ * ```ts
930
+ * import { renderPremise } from '@orkestrel/qualifier'
931
+ *
932
+ * renderPremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })
933
+ * // 'age is more than 18 → met'
934
+ *
935
+ * renderPremise({ description: 'Applicant is enrolled', met: true })
936
+ * // 'Applicant is enrolled → met'
937
+ * ```
938
+ */
939
+ export declare function renderPremise(entry: Premise, labels?: Readonly<Record<string, string>>): string;
940
+
941
+ /**
942
+ * Renders a structured or scalar expected value display-neutrally.
943
+ *
944
+ * @remarks
945
+ * An array renders as its elements, each `String`-coerced, joined with `', '`.
946
+ * A reason `Bounds`-shaped record (the `between` / `outside` expected value)
947
+ * renders its present sides joined with the word `and`; an absent side is
948
+ * 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
950
+ * message-token-only behavior).
951
+ *
952
+ * @param value - The value to render
953
+ * @returns A display-neutral rendering
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * import { renderValue } from '@orkestrel/qualifier'
958
+ *
959
+ * renderValue([18, 25, 40]) // '18, 25, 40'
960
+ * renderValue({ minimum: 18, maximum: 65 }) // '18 and 65'
961
+ * renderValue(42) // '42'
962
+ * ```
963
+ */
964
+ export declare function renderValue(value: unknown): string;
965
+
966
+ /**
967
+ * Builds rich premises for one fired {@link Rule} by walking its premise atoms
968
+ * and re-evaluating each against the working subject.
969
+ *
970
+ * @remarks
971
+ * A reason rule result carries only booleans, so this is the qualifier's own
972
+ * premise-audit projection: each authored premise expression is flattened to its
973
+ * 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
975
+ * array value is content-free and is skipped.
976
+ *
977
+ * @param rule - The authored rule
978
+ * @param working - The working subject to evaluate against
979
+ * @param evaluator - The shared reason check evaluator
980
+ * @param labels - Optional field-to-label overrides, keyed by dot-joined field
981
+ * @returns A fresh, ordered list of rich premises
982
+ *
983
+ * @example
984
+ * ```ts
985
+ * import { ruleToPremises } from '@orkestrel/qualifier'
986
+ * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'
987
+ *
988
+ * const gates = createLogicalDefinition('gates', 'Gates', [
989
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
990
+ * ])
991
+ * const evaluator = createEvaluator()
992
+ * const premises = ruleToPremises(gates.rules[0], { licensed: false }, evaluator)
993
+ *
994
+ * premises[0]?.met // true
995
+ * ```
996
+ */
997
+ export declare function ruleToPremises(rule: Rule, working: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): readonly Premise[];
998
+
999
+ /** Represents an authored consequence for one rule in one logical pass. */
1000
+ export declare interface Ruling {
1001
+ readonly id: string;
1002
+ readonly pass: string;
1003
+ readonly rule: string;
1004
+ readonly effect: QualificationEffect;
1005
+ readonly scope?: string;
1006
+ readonly message?: string;
1007
+ }
1008
+
1009
+ /** Carries the optional fields `createRuling` accepts. */
1010
+ export declare interface RulingInput {
1011
+ readonly scope?: string;
1012
+ readonly message?: string;
1013
+ }
1014
+
1015
+ /**
1016
+ * Joins a ruling, its logical rule result, the pass, the pre-projection subject,
1017
+ * and an evaluator into a {@link Finding}.
1018
+ *
1019
+ * @remarks
1020
+ * `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
1022
+ * derived can never satisfy its own reported premise. The message interpolates
1023
+ * against that snapshot.
1024
+ *
1025
+ * @param ruling - The authored ruling
1026
+ * @param pass - The pass the ruling's rule lives in
1027
+ * @param result - The pass's evaluated logical result
1028
+ * @param subject - The pre-projection subject snapshot the pass evaluated
1029
+ * @param evaluator - The shared reason check evaluator
1030
+ * @param labels - Optional field-to-label overrides, keyed by dot-joined field
1031
+ * @returns A fresh finding
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * import { rulingToFinding } from '@orkestrel/qualifier'
1036
+ * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'
1037
+ *
1038
+ * const gates = createLogicalDefinition('gates', 'Gates', [
1039
+ * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),
1040
+ * ])
1041
+ * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }
1042
+ * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true] }], count: 1, success: true, trace: [], errors: [] }
1043
+ * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())
1044
+ *
1045
+ * finding.applied // true
1046
+ * ```
1047
+ */
1048
+ export declare function rulingToFinding(ruling: Ruling, pass: QualificationPass, result: LogicalResult, subject: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): Finding;
833
1049
 
834
- export { }
1050
+ export { }