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