@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.
@@ -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 { }