@orkestrel/qualifier 0.0.1
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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/src/core/index.cjs +747 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +569 -0
- package/dist/src/core/index.d.ts +569 -0
- package/dist/src/core/index.js +713 -0
- package/dist/src/core/index.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,569 @@
|
|
|
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';
|
|
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 { Rule } from '@orkestrel/reason';
|
|
18
|
+
import { Subject } from '@orkestrel/reason';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Assert a value is a valid qualification {@link Subject}, narrowing it in place.
|
|
22
|
+
*
|
|
23
|
+
* @param value - The candidate subject to validate
|
|
24
|
+
* @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or
|
|
25
|
+
* when it already carries the reserved `qualification` key
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertSubject(value: unknown): asserts value is Subject;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return the most severe {@link Eligibility} in a list.
|
|
31
|
+
*
|
|
32
|
+
* @param eligibilities - The eligibilities to combine
|
|
33
|
+
* @returns The most severe eligibility, or `'eligible'` for an empty list
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { combineEligibilities } from '@orkestrel/qualifier'
|
|
38
|
+
*
|
|
39
|
+
* combineEligibilities(['eligible', 'referral']) // 'referral'
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function combineEligibilities(eligibilities: Eligibility[]): Eligibility;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create one qualifier over a reason engine.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* A standalone qualifier creates and OWNS one shared quantitative-plus-logical
|
|
49
|
+
* reason engine, destroying it on `destroy()`. An injected `options.engine`
|
|
50
|
+
* remains caller-owned and is never destroyed. Validation is on by default.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Optional injected engine, validation, labels, and emitter hooks
|
|
53
|
+
* @returns A {@link QualifierInterface}
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { createQualifier } from '@orkestrel/qualifier'
|
|
58
|
+
*
|
|
59
|
+
* const qualifier = createQualifier()
|
|
60
|
+
* qualifier.qualify({ id: 'risk-1' }, definition)
|
|
61
|
+
* qualifier.destroy()
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function createQualifier(options?: QualifierOptions): QualifierInterface;
|
|
65
|
+
|
|
66
|
+
/** Default definition validation policy for `createQualifier` / `Qualifier.qualify`. */
|
|
67
|
+
export declare const DEFAULT_QUALIFIER_VALIDATE = true;
|
|
68
|
+
|
|
69
|
+
/** One quantitative pass's audit result. */
|
|
70
|
+
export declare interface Derivation {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly value: number;
|
|
73
|
+
readonly success: boolean;
|
|
74
|
+
readonly trace: readonly string[];
|
|
75
|
+
readonly errors: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Derive global eligibility from applied, unscoped findings.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* Only findings that both applied and carry no `scope` shape global eligibility.
|
|
83
|
+
* A failed pass contributes a synthetic `referral` (fail-closed), passed through
|
|
84
|
+
* `failed`.
|
|
85
|
+
*
|
|
86
|
+
* @param findings - The resolved findings
|
|
87
|
+
* @param failed - Whether an operational pass failure occurred
|
|
88
|
+
* @returns The most severe global eligibility
|
|
89
|
+
*/
|
|
90
|
+
export declare function deriveFindingEligibility(findings: Finding[], failed?: boolean): Eligibility;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Derive one eligibility per finding scope.
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* Only applied, scoped findings contribute. Each named scope combines its own
|
|
97
|
+
* findings' impacts by severity. An unmentioned scope is absent (eligible by
|
|
98
|
+
* default at the program layer).
|
|
99
|
+
*
|
|
100
|
+
* @param findings - The resolved findings
|
|
101
|
+
* @returns A fresh record of scope to its combined eligibility
|
|
102
|
+
*/
|
|
103
|
+
export declare function deriveScopeEligibilities(findings: Finding[]): Readonly<Record<string, Eligibility>>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Describe a {@link Premise} comparison as a display-neutral verb phrase.
|
|
107
|
+
*
|
|
108
|
+
* @param comparison - The comparison to describe
|
|
109
|
+
* @returns A display-neutral phrase
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { describeComparison } from '@orkestrel/qualifier'
|
|
114
|
+
*
|
|
115
|
+
* describeComparison('above') // 'is more than'
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function describeComparison(comparison: NonNullable<Premise['comparison']>): string;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Render one {@link Premise} into a display-neutral sentence.
|
|
122
|
+
*
|
|
123
|
+
* @param entry - The premise to render
|
|
124
|
+
* @param labels - Optional field-to-label overrides, keyed by dot-joined field
|
|
125
|
+
* @returns A display-neutral sentence
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* import { describePremise } from '@orkestrel/qualifier'
|
|
130
|
+
*
|
|
131
|
+
* describePremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })
|
|
132
|
+
* // 'age is more than 18 → met'
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function describePremise(entry: Premise, labels?: Readonly<Record<string, string>>): string;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Render a structured or scalar expected value display-neutrally.
|
|
139
|
+
*
|
|
140
|
+
* @remarks
|
|
141
|
+
* An array renders as its elements, each `String`-coerced, joined with `', '`.
|
|
142
|
+
* A reason `Bounds`-shaped record (the `between` / `outside` expected value)
|
|
143
|
+
* renders its present sides joined with the word `and`; an absent side is
|
|
144
|
+
* omitted. A finite number and everything else fall back to plain `String()` —
|
|
145
|
+
* a finite number is NOT grouped here (grouping is {@link interpolateMessage}'s
|
|
146
|
+
* message-token-only behavior).
|
|
147
|
+
*
|
|
148
|
+
* @param value - The value to render
|
|
149
|
+
* @returns A display-neutral rendering
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { describeValue } from '@orkestrel/qualifier'
|
|
154
|
+
*
|
|
155
|
+
* describeValue([18, 25, 40]) // '18, 25, 40'
|
|
156
|
+
* describeValue({ minimum: 18, maximum: 65 }) // '18 and 65'
|
|
157
|
+
* describeValue(42) // '42'
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export declare function describeValue(value: unknown): string;
|
|
161
|
+
|
|
162
|
+
/** The eligibility impact of each {@link QualificationEffect}; `condition` remains eligible. */
|
|
163
|
+
export declare const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>>;
|
|
164
|
+
|
|
165
|
+
/** The pre-rating outcome axis. */
|
|
166
|
+
export declare type Eligibility = 'eligible' | 'ineligible' | 'referral';
|
|
167
|
+
|
|
168
|
+
/** Eligibility severity order — most to least severe. */
|
|
169
|
+
export declare const ELIGIBILITY_PRECEDENCE: readonly Eligibility[];
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find duplicate ids in a list, once each.
|
|
173
|
+
*
|
|
174
|
+
* @param ids - The ids to scan
|
|
175
|
+
* @returns A fresh list of ids that appear more than once
|
|
176
|
+
*/
|
|
177
|
+
export declare function findDuplicateIds(ids: readonly string[]): readonly string[];
|
|
178
|
+
|
|
179
|
+
/** One resolved ruling. */
|
|
180
|
+
export declare interface Finding {
|
|
181
|
+
readonly id: string;
|
|
182
|
+
readonly pass: string;
|
|
183
|
+
readonly rule: string;
|
|
184
|
+
readonly effect: QualificationEffect;
|
|
185
|
+
readonly scope?: string;
|
|
186
|
+
readonly applied: boolean;
|
|
187
|
+
readonly message?: string;
|
|
188
|
+
readonly premises: readonly Premise[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find rulings whose pass or rule does not exist, whose pass is not logical, or
|
|
193
|
+
* a pass id shadowing the reserved {@link QUALIFICATION_KEY}.
|
|
194
|
+
*
|
|
195
|
+
* @param definition - The qualification definition to check
|
|
196
|
+
* @returns A fresh list of reference-error messages
|
|
197
|
+
*/
|
|
198
|
+
export declare function findMissingReferences(definition: QualificationDefinition): readonly string[];
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Locate an authored {@link Rule} by id.
|
|
202
|
+
*
|
|
203
|
+
* @param definition - The logical definition to search
|
|
204
|
+
* @param id - The rule id
|
|
205
|
+
* @returns The matching rule, or `undefined`
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* import { findRule } from '@orkestrel/qualifier'
|
|
210
|
+
* import { atom, logicalDefinition, rule } from '@orkestrel/reason'
|
|
211
|
+
*
|
|
212
|
+
* const gates = logicalDefinition('gates', 'Gates', [
|
|
213
|
+
* rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
|
|
214
|
+
* ])
|
|
215
|
+
*
|
|
216
|
+
* findRule(gates, 'licensed')?.id // 'licensed'
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export declare function findRule(definition: LogicalDefinition, id: string): Rule | undefined;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Determine whether a subject already owns the reserved {@link QUALIFICATION_KEY}.
|
|
223
|
+
*
|
|
224
|
+
* @param subject - The subject to check
|
|
225
|
+
* @returns `true` when the subject owns the reserved key
|
|
226
|
+
*/
|
|
227
|
+
export declare function hasReservedKey(subject: Subject): boolean;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Interpolate `{{dotted.path}}` tokens in a message template against a subject.
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* Each token is split on `.` into a `FieldPath` array and resolved with
|
|
234
|
+
* `resolveField` (a plain string field is ONE key, never dot-split — the split
|
|
235
|
+
* here is the token-to-path bridge). A finite number renders with `en-US`
|
|
236
|
+
* thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.
|
|
237
|
+
* An UNRESOLVED path renders as the empty string.
|
|
238
|
+
*
|
|
239
|
+
* @param template - The message template carrying `{{dotted.path}}` tokens
|
|
240
|
+
* @param subject - The record tokens resolve against
|
|
241
|
+
* @returns The template with every token replaced
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* import { interpolateMessage } from '@orkestrel/qualifier'
|
|
246
|
+
*
|
|
247
|
+
* interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'
|
|
248
|
+
* interpolateMessage('Missing {{gone}}', {}) // 'Missing '
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export declare function interpolateMessage(template: string, subject: Readonly<Record<string, unknown>>): string;
|
|
252
|
+
|
|
253
|
+
/** Determine whether a value is an {@link Eligibility} literal. */
|
|
254
|
+
export declare const isEligibility: Guard<"eligible" | "ineligible" | "referral">;
|
|
255
|
+
|
|
256
|
+
/** Determine whether a value is an exact {@link QualificationDefinition} record. */
|
|
257
|
+
export declare function isQualificationDefinition(value: unknown): value is QualificationDefinition;
|
|
258
|
+
|
|
259
|
+
/** Determine whether a value is a {@link QualificationEffect} literal. */
|
|
260
|
+
export declare const isQualificationEffect: Guard<"referral" | "restriction" | "condition">;
|
|
261
|
+
|
|
262
|
+
/** Determine whether a value is a {@link QualificationPass} (a quantitative or logical definition). */
|
|
263
|
+
export declare function isQualificationPass(value: unknown): value is QualificationPass;
|
|
264
|
+
|
|
265
|
+
/** Narrow a caught value to a {@link QualifierError}. */
|
|
266
|
+
export declare function isQualifierError(value: unknown): value is QualifierError;
|
|
267
|
+
|
|
268
|
+
/** Determine whether a value is an exact {@link Ruling} record. */
|
|
269
|
+
export declare function isRuling(value: unknown): value is Ruling;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Build rich premises for one fired {@link Rule} by walking its premise atoms
|
|
273
|
+
* and re-evaluating each against the working subject.
|
|
274
|
+
*
|
|
275
|
+
* @remarks
|
|
276
|
+
* A reason rule result carries only booleans, so this is the qualifier's own
|
|
277
|
+
* premise-audit projection: each authored premise expression is flattened to its
|
|
278
|
+
* atom leaves via `extractAtoms`, and each leaf's `Check` is re-evaluated through
|
|
279
|
+
* the injected `evaluator`. A membership check (`any` / `none`) over an EMPTY
|
|
280
|
+
* array value is content-free and is skipped.
|
|
281
|
+
*
|
|
282
|
+
* @param rule - The authored rule
|
|
283
|
+
* @param working - The working subject to evaluate against
|
|
284
|
+
* @param evaluator - The shared reason check evaluator
|
|
285
|
+
* @param labels - Optional field-to-label overrides, keyed by dot-joined field
|
|
286
|
+
* @returns A fresh, ordered list of rich premises
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* import { logicalPremises } from '@orkestrel/qualifier'
|
|
291
|
+
* import { atom, createEvaluator, logicalDefinition, rule } from '@orkestrel/reason'
|
|
292
|
+
*
|
|
293
|
+
* const gates = logicalDefinition('gates', 'Gates', [
|
|
294
|
+
* rule('licensed', [atom('licensed', 'equals', false)], atom('blocked', 'equals', true)),
|
|
295
|
+
* ])
|
|
296
|
+
* const evaluator = createEvaluator()
|
|
297
|
+
* const premises = logicalPremises(gates.rules[0], { licensed: false }, evaluator)
|
|
298
|
+
*
|
|
299
|
+
* premises[0]?.met // true
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
export declare function logicalPremises(rule: Rule, working: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): readonly Premise[];
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Copy-on-write merge one pass projection into the context.
|
|
306
|
+
*
|
|
307
|
+
* @param context - The current projection context
|
|
308
|
+
* @param id - The pass id to project under
|
|
309
|
+
* @param projection - The pass's working projection
|
|
310
|
+
* @returns A fresh context with the projection added
|
|
311
|
+
*/
|
|
312
|
+
export declare function mergeQualificationContext(context: QualificationContext, id: string, projection: QualificationProjection): QualificationContext;
|
|
313
|
+
|
|
314
|
+
/** Display-neutral checked evidence. */
|
|
315
|
+
export declare interface Premise {
|
|
316
|
+
readonly field?: FieldPath;
|
|
317
|
+
readonly label?: string;
|
|
318
|
+
readonly description?: string;
|
|
319
|
+
readonly comparison?: Comparison;
|
|
320
|
+
readonly expected?: unknown;
|
|
321
|
+
readonly actual?: unknown;
|
|
322
|
+
readonly met?: boolean;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Build a {@link Premise} from an authored {@link Check} and its evaluated
|
|
327
|
+
* `CheckResult`.
|
|
328
|
+
*
|
|
329
|
+
* @param check - The authored check
|
|
330
|
+
* @param result - The evaluated check result carrying the resolved `actual` and `met`
|
|
331
|
+
* @param labels - Optional field-to-label overrides, keyed by dot-joined field
|
|
332
|
+
* @returns A fresh premise
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* import { premiseCheck } from '@orkestrel/qualifier'
|
|
337
|
+
* import { check } from '@orkestrel/reason'
|
|
338
|
+
*
|
|
339
|
+
* const authored = check('age', 'above', 18)
|
|
340
|
+
* premiseCheck(authored, { field: 'age', actual: 25, met: true })
|
|
341
|
+
* // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
export declare function premiseCheck(check: Check, result: CheckResult, labels?: Readonly<Record<string, string>>): Premise;
|
|
345
|
+
|
|
346
|
+
/** The reserved internal projection namespace a pass's working projection is written under. */
|
|
347
|
+
export declare const QUALIFICATION_KEY = "qualification";
|
|
348
|
+
|
|
349
|
+
/** The internal projection record stored under `QUALIFICATION_KEY`. */
|
|
350
|
+
export declare type QualificationContext = Readonly<Record<string, QualificationProjection>>;
|
|
351
|
+
|
|
352
|
+
/** A pure authored qualification definition. */
|
|
353
|
+
export declare interface QualificationDefinition {
|
|
354
|
+
readonly id: string;
|
|
355
|
+
readonly name: string;
|
|
356
|
+
readonly description?: string;
|
|
357
|
+
readonly passes: readonly QualificationPass[];
|
|
358
|
+
readonly rulings?: readonly Ruling[];
|
|
359
|
+
readonly metadata?: Readonly<Record<string, JSONValue>>;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Build a {@link QualificationDefinition}.
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* Returns a fresh top-level definition, omitting absent optional keys;
|
|
367
|
+
* `passes` and `rulings` arrays are copied, and record `metadata` is
|
|
368
|
+
* shallow-copied so nested values are not deep-cloned.
|
|
369
|
+
*
|
|
370
|
+
* @param id - The definition id
|
|
371
|
+
* @param name - The display name
|
|
372
|
+
* @param passes - The ordered qualification passes
|
|
373
|
+
* @param input - Optional description, rulings, and metadata
|
|
374
|
+
* @returns A fresh qualification definition
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* import { qualificationDefinition } from '@orkestrel/qualifier'
|
|
379
|
+
*
|
|
380
|
+
* qualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
export declare function qualificationDefinition(id: string, name: string, passes: QualificationPass[], input?: QualificationInput): QualificationDefinition;
|
|
384
|
+
|
|
385
|
+
/** An authored ruling's eligibility impact. */
|
|
386
|
+
export declare type QualificationEffect = 'restriction' | 'referral' | 'condition';
|
|
387
|
+
|
|
388
|
+
/** Optional fields accepted by `qualificationDefinition`. */
|
|
389
|
+
export declare interface QualificationInput {
|
|
390
|
+
readonly description?: string;
|
|
391
|
+
readonly rulings?: readonly Ruling[];
|
|
392
|
+
readonly metadata?: Readonly<Record<string, JSONValue>>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** One ordered derivation or rule pass. */
|
|
396
|
+
export declare type QualificationPass = QuantitativeDefinition | LogicalDefinition;
|
|
397
|
+
|
|
398
|
+
/** One pass's internal working projection. */
|
|
399
|
+
export declare type QualificationProjection = number | boolean | Readonly<Record<string, unknown>>;
|
|
400
|
+
|
|
401
|
+
/** One subject's complete qualification outcome. */
|
|
402
|
+
export declare interface QualificationResult {
|
|
403
|
+
readonly id: string;
|
|
404
|
+
readonly name: string;
|
|
405
|
+
readonly eligibility: Eligibility;
|
|
406
|
+
readonly scopes: Readonly<Record<string, Eligibility>>;
|
|
407
|
+
readonly findings: readonly Finding[];
|
|
408
|
+
readonly derivations: readonly Derivation[];
|
|
409
|
+
readonly success: boolean;
|
|
410
|
+
readonly trace: readonly string[];
|
|
411
|
+
readonly errors: readonly string[];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Wrap a {@link QualificationContext} under {@link QUALIFICATION_KEY}.
|
|
416
|
+
*
|
|
417
|
+
* @param context - The accumulated projection context
|
|
418
|
+
* @returns A record carrying the context under the reserved key
|
|
419
|
+
*/
|
|
420
|
+
export declare function qualificationToRecord(context: QualificationContext): Readonly<Record<string, QualificationContext>>;
|
|
421
|
+
|
|
422
|
+
/** Semantic definition validation. */
|
|
423
|
+
export declare interface QualificationValidationResult {
|
|
424
|
+
readonly valid: boolean;
|
|
425
|
+
readonly errors: readonly string[];
|
|
426
|
+
readonly warnings: readonly string[];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* A qualifier — runs ordered passes over one reason engine and returns
|
|
431
|
+
* pre-rating eligibility.
|
|
432
|
+
*
|
|
433
|
+
* @remarks
|
|
434
|
+
* The engine is OWNED when self-created (destroyed on `destroy()`) and borrowed
|
|
435
|
+
* when injected (never destroyed). `qualify` builds its working subject through
|
|
436
|
+
* copy-on-write overlays only — the caller's `subject` is never mutated, and the
|
|
437
|
+
* qualifier's internal projection is discarded after each call.
|
|
438
|
+
*/
|
|
439
|
+
export declare class Qualifier implements QualifierInterface {
|
|
440
|
+
#private;
|
|
441
|
+
constructor(options?: QualifierOptions);
|
|
442
|
+
get emitter(): EmitterInterface<QualifierEventMap>;
|
|
443
|
+
qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
|
|
444
|
+
validate(definition: QualificationDefinition): QualificationValidationResult;
|
|
445
|
+
destroy(): void;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* A coded programmer error thrown by the qualifier layer.
|
|
450
|
+
*
|
|
451
|
+
* @remarks
|
|
452
|
+
* `DEFINITION` — a qualification definition failed semantic validation.
|
|
453
|
+
* `MISMATCH` — a subject is not a record or already carries the reserved
|
|
454
|
+
* `qualification` key. `DESTROYED` — use of a destroyed qualifier.
|
|
455
|
+
*/
|
|
456
|
+
export declare class QualifierError extends Error {
|
|
457
|
+
readonly code: QualifierErrorCode;
|
|
458
|
+
readonly context: unknown;
|
|
459
|
+
constructor(code: QualifierErrorCode, message: string, context?: unknown);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** A coded {@link QualifierError} programmer-error code. */
|
|
463
|
+
export declare type QualifierErrorCode = 'DEFINITION' | 'MISMATCH' | 'DESTROYED';
|
|
464
|
+
|
|
465
|
+
/** The push observation surface of a {@link QualifierInterface} (AGENTS §13). */
|
|
466
|
+
export declare type QualifierEventMap = {
|
|
467
|
+
readonly derive: readonly [derivation: Derivation];
|
|
468
|
+
readonly finding: readonly [finding: Finding];
|
|
469
|
+
readonly qualify: readonly [result: QualificationResult];
|
|
470
|
+
readonly destroy: readonly [];
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/** One qualifier — owns or borrows one reason engine and returns pre-rating eligibility. */
|
|
474
|
+
export declare interface QualifierInterface {
|
|
475
|
+
readonly emitter: EmitterInterface<QualifierEventMap>;
|
|
476
|
+
qualify(subject: Subject, definition: QualificationDefinition): QualificationResult;
|
|
477
|
+
validate(definition: QualificationDefinition): QualificationValidationResult;
|
|
478
|
+
destroy(): void;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Options for `createQualifier` / the `Qualifier` constructor. */
|
|
482
|
+
export declare interface QualifierOptions {
|
|
483
|
+
readonly engine?: ReasonInterface;
|
|
484
|
+
readonly validate?: boolean;
|
|
485
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
486
|
+
readonly on?: EmitterHooks<QualifierEventMap>;
|
|
487
|
+
readonly error?: EmitterErrorHandler;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Project a quantitative result into a {@link Derivation} audit record.
|
|
492
|
+
*
|
|
493
|
+
* @param id - The pass id
|
|
494
|
+
* @param result - The evaluated quantitative result
|
|
495
|
+
* @returns A fresh derivation
|
|
496
|
+
*/
|
|
497
|
+
export declare function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Project one reason result into the internal qualification namespace.
|
|
501
|
+
*
|
|
502
|
+
* @remarks
|
|
503
|
+
* A quantitative result contributes its numeric `value`. A logical result
|
|
504
|
+
* contributes a record of its `conclusion` plus every applied rule's asserted
|
|
505
|
+
* conclusion fields (`extractConclusions`). A mismatched pairing contributes
|
|
506
|
+
* `false`. The projection is pure — it never mutates the subject or the result.
|
|
507
|
+
*
|
|
508
|
+
* @param definition - The pass definition that produced the result
|
|
509
|
+
* @param result - The evaluated reason result
|
|
510
|
+
* @returns The pass's working projection
|
|
511
|
+
*/
|
|
512
|
+
export declare function reasonResultToProjection(definition: QualificationPass, result: ReasonResult): QualificationProjection;
|
|
513
|
+
|
|
514
|
+
/** An authored consequence for one rule in one logical pass. */
|
|
515
|
+
export declare interface Ruling {
|
|
516
|
+
readonly id: string;
|
|
517
|
+
readonly pass: string;
|
|
518
|
+
readonly rule: string;
|
|
519
|
+
readonly effect: QualificationEffect;
|
|
520
|
+
readonly scope?: string;
|
|
521
|
+
readonly message?: string;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Build a {@link Ruling} — one authored consequence for one rule in one pass.
|
|
526
|
+
*
|
|
527
|
+
* @param id - The ruling id
|
|
528
|
+
* @param pass - The logical pass id the rule lives in
|
|
529
|
+
* @param rule - The rule id whose firing this ruling reacts to
|
|
530
|
+
* @param effect - The eligibility impact when the rule fires
|
|
531
|
+
* @param input - Optional rating scope and message template
|
|
532
|
+
* @returns A fresh ruling
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* ```ts
|
|
536
|
+
* import { rulingDefinition } from '@orkestrel/qualifier'
|
|
537
|
+
*
|
|
538
|
+
* rulingDefinition('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
export declare function rulingDefinition(id: string, pass: string, rule: string, effect: QualificationEffect, input?: RulingInput): Ruling;
|
|
542
|
+
|
|
543
|
+
/** Optional fields accepted by `rulingDefinition`. */
|
|
544
|
+
export declare interface RulingInput {
|
|
545
|
+
readonly scope?: string;
|
|
546
|
+
readonly message?: string;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Join a ruling, its logical rule result, the pass, the pre-projection subject,
|
|
551
|
+
* and an evaluator into a {@link Finding}.
|
|
552
|
+
*
|
|
553
|
+
* @remarks
|
|
554
|
+
* `applied` reflects whether the ruling's rule fired. Premises are re-evaluated
|
|
555
|
+
* against the SAME subject snapshot the pass evaluated, so a conclusion the pass
|
|
556
|
+
* derived can never satisfy its own reported premise. The message interpolates
|
|
557
|
+
* against that snapshot.
|
|
558
|
+
*
|
|
559
|
+
* @param ruling - The authored ruling
|
|
560
|
+
* @param pass - The pass the ruling's rule lives in
|
|
561
|
+
* @param result - The pass's evaluated logical result
|
|
562
|
+
* @param subject - The pre-projection subject snapshot the pass evaluated
|
|
563
|
+
* @param evaluator - The shared reason check evaluator
|
|
564
|
+
* @param labels - Optional field-to-label overrides, keyed by dot-joined field
|
|
565
|
+
* @returns A fresh finding
|
|
566
|
+
*/
|
|
567
|
+
export declare function rulingToFinding(ruling: Ruling, pass: QualificationPass, result: LogicalResult, subject: Subject, evaluator: EvaluatorInterface, labels?: Readonly<Record<string, string>>): Finding;
|
|
568
|
+
|
|
569
|
+
export { }
|