@orkestrel/qualifier 0.0.17 → 0.0.19
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/dist/src/core/index.cjs +47 -34
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +2 -2
- package/dist/src/core/index.d.ts +2 -2
- package/dist/src/core/index.js +48 -35
- package/dist/src/core/index.js.map +1 -1
- package/package.json +16 -16
package/dist/src/core/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_reason = require("@orkestrel/reason");
|
|
4
4
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
5
|
-
|
|
5
|
+
// src/core/constants.ts
|
|
6
6
|
/**
|
|
7
7
|
* Holds the default definition validation policy for `createQualifier` and
|
|
8
8
|
* `Qualifier.qualify`, `true`.
|
|
@@ -28,8 +28,7 @@ var EFFECT_ELIGIBILITIES = Object.freeze({
|
|
|
28
28
|
referral: "referral",
|
|
29
29
|
condition: "eligible"
|
|
30
30
|
});
|
|
31
|
-
|
|
32
|
-
//#region src/core/errors.ts
|
|
31
|
+
// src/core/errors.ts
|
|
33
32
|
/**
|
|
34
33
|
* Represents a coded programmer error thrown by the qualifier layer, carrying a
|
|
35
34
|
* `QualifierErrorCode` and an optional context record.
|
|
@@ -51,6 +50,7 @@ var QualifierError = class extends Error {
|
|
|
51
50
|
context;
|
|
52
51
|
constructor(code, message, context) {
|
|
53
52
|
super(message);
|
|
53
|
+
_orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/qualifier.QualifierError"), { value: this });
|
|
54
54
|
this.name = "QualifierError";
|
|
55
55
|
this.code = code;
|
|
56
56
|
this.context = context;
|
|
@@ -60,13 +60,23 @@ var QualifierError = class extends Error {
|
|
|
60
60
|
* Narrows a caught value to a {@link QualifierError}.
|
|
61
61
|
*
|
|
62
62
|
* @param value - The caught value to test
|
|
63
|
-
* @returns True if
|
|
63
|
+
* @returns True if the error carries its own identity brand and declared members; false otherwise
|
|
64
64
|
*/
|
|
65
65
|
function isQualifierError(value) {
|
|
66
|
-
return (0, _orkestrel_contract.
|
|
66
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
67
|
+
if (!(value instanceof Error) || _orkestrel_contract.INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
68
|
+
if (_orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/qualifier.QualifierError"))?.value !== value) return false;
|
|
69
|
+
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
70
|
+
if (code !== "DEFINITION" && code !== "MISMATCH" && code !== "DESTROYED" && code !== "ENGINE") return false;
|
|
71
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
72
|
+
if (context === void 0) return true;
|
|
73
|
+
if (!(0, _orkestrel_contract.isObject)(context) && !(0, _orkestrel_contract.isFunction)(context)) return false;
|
|
74
|
+
const pass = _orkestrel_contract.INTRINSICS.reflect.read(context, "pass");
|
|
75
|
+
const definition = _orkestrel_contract.INTRINSICS.reflect.read(context, "definition");
|
|
76
|
+
return (pass === void 0 || typeof pass === "string") && (definition === void 0 || typeof definition === "string");
|
|
77
|
+
});
|
|
67
78
|
}
|
|
68
|
-
|
|
69
|
-
//#region src/core/validators.ts
|
|
79
|
+
// src/core/validators.ts
|
|
70
80
|
/**
|
|
71
81
|
* Determines whether a value is an {@link Eligibility} literal.
|
|
72
82
|
*
|
|
@@ -262,8 +272,7 @@ function isQualificationDefinition(value) {
|
|
|
262
272
|
"metadata"
|
|
263
273
|
])(value);
|
|
264
274
|
}
|
|
265
|
-
|
|
266
|
-
//#region src/core/helpers.ts
|
|
275
|
+
// src/core/helpers.ts
|
|
267
276
|
/**
|
|
268
277
|
* Interpolates `{{dotted.path}}` tokens in a message template against a subject.
|
|
269
278
|
*
|
|
@@ -899,8 +908,7 @@ function describeUnreadDerivations(definition) {
|
|
|
899
908
|
});
|
|
900
909
|
return warnings;
|
|
901
910
|
}
|
|
902
|
-
|
|
903
|
-
//#region src/core/Qualifier.ts
|
|
911
|
+
// src/core/Qualifier.ts
|
|
904
912
|
/**
|
|
905
913
|
* Runs ordered passes over one reason engine and returns eligibility.
|
|
906
914
|
*
|
|
@@ -1069,22 +1077,21 @@ var Qualifier = class {
|
|
|
1069
1077
|
}
|
|
1070
1078
|
#qualify(subject, definition) {
|
|
1071
1079
|
assertSubject(subject);
|
|
1072
|
-
|
|
1073
|
-
|
|
1080
|
+
const steps = [{
|
|
1081
|
+
context: {},
|
|
1082
|
+
subject: { ...subject },
|
|
1083
|
+
success: true
|
|
1084
|
+
}];
|
|
1074
1085
|
const findings = [];
|
|
1075
1086
|
const derivations = [];
|
|
1076
1087
|
const trace = [];
|
|
1077
1088
|
const errors = [];
|
|
1078
|
-
let failed = false;
|
|
1079
1089
|
for (const pass of definition.passes) {
|
|
1080
1090
|
this.#alive();
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
throw mapEngineError(error, pass.id);
|
|
1087
|
-
}
|
|
1091
|
+
const previous = steps.at(-1);
|
|
1092
|
+
if (previous === void 0) throw new Error("Qualification has no initial step");
|
|
1093
|
+
const evaluated = previous.subject;
|
|
1094
|
+
const result = this.#reason(evaluated, pass);
|
|
1088
1095
|
trace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`));
|
|
1089
1096
|
errors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`));
|
|
1090
1097
|
if (result.reasoning === "quantitative") {
|
|
@@ -1101,18 +1108,19 @@ var Qualifier = class {
|
|
|
1101
1108
|
}
|
|
1102
1109
|
}
|
|
1103
1110
|
const projection = reasonResultToProjection(pass, result);
|
|
1104
|
-
context = mergeQualificationContext(context, pass.id, projection);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1111
|
+
const context = mergeQualificationContext(previous.context, pass.id, projection);
|
|
1112
|
+
steps.push({
|
|
1113
|
+
context,
|
|
1114
|
+
subject: {
|
|
1115
|
+
...subject,
|
|
1116
|
+
...qualificationToRecord(context)
|
|
1117
|
+
},
|
|
1118
|
+
success: result.success
|
|
1119
|
+
});
|
|
1120
|
+
if (!result.success) break;
|
|
1113
1121
|
if (deriveFindingEligibility(findings) === "ineligible") break;
|
|
1114
1122
|
}
|
|
1115
|
-
const success =
|
|
1123
|
+
const success = steps.every((step) => step.success) && errors.length === 0;
|
|
1116
1124
|
const scopes = deriveScopeEligibilities(findings);
|
|
1117
1125
|
const qualification = Object.freeze({
|
|
1118
1126
|
id: definition.id,
|
|
@@ -1128,12 +1136,18 @@ var Qualifier = class {
|
|
|
1128
1136
|
this.#emitter.emit("qualify", qualification);
|
|
1129
1137
|
return qualification;
|
|
1130
1138
|
}
|
|
1139
|
+
#reason(subject, pass) {
|
|
1140
|
+
try {
|
|
1141
|
+
return this.#engine.reason(subject, pass);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
throw mapEngineError(error, pass.id);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1131
1146
|
#alive() {
|
|
1132
1147
|
if (this.#destroyed) throw new QualifierError("DESTROYED", "Qualifier has been destroyed");
|
|
1133
1148
|
}
|
|
1134
1149
|
};
|
|
1135
|
-
|
|
1136
|
-
//#region src/core/factories.ts
|
|
1150
|
+
// src/core/factories.ts
|
|
1137
1151
|
/**
|
|
1138
1152
|
* Creates one {@link QualifierInterface} over a reason engine.
|
|
1139
1153
|
*
|
|
@@ -1242,7 +1256,6 @@ function createRuling(id, pass, rule, effect, input) {
|
|
|
1242
1256
|
...input?.message === void 0 ? {} : { message: input.message }
|
|
1243
1257
|
};
|
|
1244
1258
|
}
|
|
1245
|
-
//#endregion
|
|
1246
1259
|
exports.DEFAULT_QUALIFIER_VALIDATE = DEFAULT_QUALIFIER_VALIDATE;
|
|
1247
1260
|
exports.EFFECT_ELIGIBILITIES = EFFECT_ELIGIBILITIES;
|
|
1248
1261
|
exports.ELIGIBILITY_PRECEDENCE = ELIGIBILITY_PRECEDENCE;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["isInstance","literalOf","whereOf","objectOf","isFieldPath","isString","isComparison","isBoolean","arrayOf","isNumber","isQuantitativeDefinition","isLogicalDefinition","recordOf","isRecord","resolveField","isFiniteNumber","isArray","isBounds","formatField","extractAtoms","extractConclusions","isRecord","isReasonError","isError","Emitter","createEvaluator","createReason","createQuantitativeReasoner","createLogicalReasoner","findDuplicates"],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Qualifier.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { Eligibility, QualificationEffect } from './types.js'\n\n/**\n * Holds the default definition validation policy for `createQualifier` and\n * `Qualifier.qualify`, `true`.\n */\nexport const DEFAULT_QUALIFIER_VALIDATE = true\n\n/**\n * Names `'qualification'`, the reserved internal projection namespace a pass's working\n * projection is written under.\n */\nexport const QUALIFICATION_KEY = 'qualification'\n\n/** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */\nexport const ELIGIBILITY_PRECEDENCE: readonly Eligibility[] = Object.freeze([\n\t'ineligible',\n\t'referral',\n\t'eligible',\n])\n\n/**\n * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to\n * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.\n */\nexport const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>> =\n\tObject.freeze({\n\t\trestriction: 'ineligible',\n\t\treferral: 'referral',\n\t\tcondition: 'eligible',\n\t})\n","import type { QualifierErrorCode, QualifierErrorContext } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Represents a coded programmer error thrown by the qualifier layer, carrying a\n * `QualifierErrorCode` and an optional context record.\n *\n * @remarks\n * `DEFINITION` — a qualification definition failed semantic validation; when\n * `qualify`'s own validation raises it, `context.definition` names the\n * definition, and when an engine throw maps to it, `context.pass` names the pass\n * and `context.cause` preserves the original throw. `MISMATCH` — a subject is\n * not a record or already carries the reserved `qualification` key.\n * `DESTROYED` — use of a destroyed qualifier; an engine-mapped one carries\n * `context.pass` and `context.cause`. `ENGINE` — the underlying reason engine\n * threw while running a pass; `context.pass` names the pass and `context.cause`\n * preserves the original throw. `context` is absent for a throw that carries no\n * payload.\n */\nexport class QualifierError extends Error {\n\treadonly code: QualifierErrorCode\n\treadonly context: QualifierErrorContext | undefined\n\n\tconstructor(code: QualifierErrorCode, message: string, context?: QualifierErrorContext) {\n\t\tsuper(message)\n\t\tthis.name = 'QualifierError'\n\t\tthis.code = code\n\t\tthis.context = context\n\t}\n}\n\n/**\n * Narrows a caught value to a {@link QualifierError}.\n *\n * @param value - The caught value to test\n * @returns True if `value` is a {@link QualifierError} instance; false otherwise\n */\nexport function isQualifierError(value: unknown): value is QualifierError {\n\treturn isInstance(value, QualifierError)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tRuling,\n} from './types.js'\nimport {\n\tarrayOf,\n\tisBoolean,\n\tisNumber,\n\tisRecord,\n\tisString,\n\tliteralOf,\n\tobjectOf,\n\trecordOf,\n\twhereOf,\n} from '@orkestrel/contract'\nimport {\n\tisComparison,\n\tisFieldPath,\n\tisLogicalDefinition,\n\tisQuantitativeDefinition,\n} from '@orkestrel/reason'\n\n/**\n * Determines whether a value is an {@link Eligibility} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the eligibility literals; false otherwise\n */\nexport const isEligibility = literalOf('eligible', 'ineligible', 'referral')\n\n/**\n * Determines whether a value is a {@link QualificationEffect} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the effect literals; false otherwise\n */\nexport const isQualificationEffect = literalOf('restriction', 'referral', 'condition')\n\n/**\n * Determines whether a value is an open string-keyed record of {@link Eligibility} values.\n *\n * @remarks\n * Every own string-named value is checked through {@link isEligibility}, including\n * non-enumerable values. Inherited and symbol-named members are left unchecked because they\n * are outside the own string-keyed record this guard certifies.\n *\n * @param value - The value to test\n * @returns True if `value` is a non-array object whose own string-named values are\n * eligibilities; false otherwise\n */\nexport function isEligibilityRecord(\n\tvalue: unknown,\n): value is Readonly<Record<string, Eligibility>> {\n\treturn whereOf(objectOf({}), (record): record is Readonly<Record<string, Eligibility>> =>\n\t\tObject.getOwnPropertyNames(record).every((key) => isEligibility(Reflect.get(record, key))),\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Premise}.\n *\n * @remarks\n * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked\n * when they are defined. `expected` is unchecked and may be absent or contain any value\n * because its published type is `unknown`. `actual` is unchecked and may be absent or contain\n * any value for the same reason. Unknown members are admitted, because an open result admits\n * what the published contract does not name.\n *\n * @param value - The value to test\n * @returns True if every checked premise member follows its published type; false otherwise\n */\nexport function isPremise(value: unknown): value is Premise {\n\treturn objectOf(\n\t\t{\n\t\t\tfield: isFieldPath,\n\t\t\tlabel: isString,\n\t\t\tdescription: isString,\n\t\t\tcomparison: isComparison,\n\t\t\tmet: isBoolean,\n\t\t},\n\t\t['field', 'label', 'description', 'comparison', 'met'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Finding}.\n *\n * @remarks\n * Every published member is checked, including each nested {@link Premise}. Optional `scope`\n * and `message` members may be absent or `undefined`; a present defined value must be a\n * string. Unknown members are admitted, because a foreign result implementation may add them\n * without changing the published contract.\n *\n * @param value - The value to test\n * @returns True if every published finding member is valid; false otherwise\n */\nexport function isFinding(value: unknown): value is Finding {\n\treturn objectOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tapplied: isBoolean,\n\t\t\tmessage: isString,\n\t\t\tpremises: arrayOf(isPremise),\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Derivation}.\n *\n * @remarks\n * Every published member is checked: `value` follows the published `number` type, including\n * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are\n * admitted, because a foreign result implementation may add them without changing the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published derivation member is valid; false otherwise\n */\nexport function isDerivation(value: unknown): value is Derivation {\n\treturn objectOf({\n\t\tid: isString,\n\t\tvalue: isNumber,\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is an open {@link QualificationResult} returned by a qualifier.\n *\n * @remarks\n * This guard is result-postured for values returned through a borrowed qualifier interface. It\n * checks the complete published result closure — eligibility, scope values, findings,\n * derivations, success, and the audit trail — and admits unknown members and class instances,\n * because a borrowed `QualifierInterface` implementation may add them beyond the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published qualification-result member is valid; false otherwise\n */\nexport function isQualificationResult(value: unknown): value is QualificationResult {\n\treturn objectOf({\n\t\tid: isString,\n\t\tname: isString,\n\t\teligibility: isEligibility,\n\t\tscopes: isEligibilityRecord,\n\t\tfindings: arrayOf(isFinding),\n\t\tderivations: arrayOf(isDerivation),\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).\n *\n * @remarks\n * The guard is exact and requires either complete reason shape, the quantitative definition or\n * the logical one. Nothing is left unchecked, because the reason package owns both authored\n * input shapes.\n *\n * @param value - The value to test\n * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise\n */\nexport function isQualificationPass(value: unknown): value is QualificationPass {\n\treturn isQuantitativeDefinition(value) || isLogicalDefinition(value)\n}\n\n/**\n * Determines whether a value is an exact {@link Ruling} record.\n *\n * @remarks\n * Every authored ruling member and the complete key set are checked. Nothing is left\n * unchecked, because this package owns the authored input record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every ruling member and no unknown key; false otherwise\n */\nexport function isRuling(value: unknown): value is Ruling {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tmessage: isString,\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an exact {@link QualificationDefinition} record.\n *\n * @remarks\n * Every authored definition member, the nested pass and ruling shapes, and the complete key\n * set are checked. Nothing is left unchecked, because this package owns the authored input\n * record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every definition member and no unknown key; false otherwise\n */\nexport function isQualificationDefinition(value: unknown): value is QualificationDefinition {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tname: isString,\n\t\t\tdescription: isString,\n\t\t\tpasses: arrayOf(isQualificationPass),\n\t\t\trulings: arrayOf(isRuling),\n\t\t\tmetadata: isRecord,\n\t\t},\n\t\t['description', 'rulings', 'metadata'],\n\t)(value)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationProjection,\n\tRuling,\n} from './types.js'\nimport type {\n\tCheck,\n\tCheckResult,\n\tEvaluatorInterface,\n\tLogicalDefinition,\n\tLogicalResult,\n\tQuantitativeResult,\n\tReasonResult,\n\tRule,\n\tSubject,\n} from '@orkestrel/reason'\nimport { EFFECT_ELIGIBILITIES, ELIGIBILITY_PRECEDENCE, QUALIFICATION_KEY } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport { isArray, isError, isFiniteNumber, isRecord, resolveField } from '@orkestrel/contract'\nimport {\n\textractAtoms,\n\textractConclusions,\n\tformatField,\n\tisBounds,\n\tisReasonError,\n} from '@orkestrel/reason'\n\n/**\n * Interpolates `{{dotted.path}}` tokens in a message template against a subject.\n *\n * @remarks\n * Each token is split on `.` into a `FieldPath` array and resolved with\n * `resolveField` (a plain string field is one key, never dot-split — the split\n * here is the token-to-path bridge). A finite number renders with `en-US`\n * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.\n * An unresolved path renders as the empty string.\n *\n * @param template - The message template carrying `{{dotted.path}}` tokens\n * @param subject - The record tokens resolve against\n * @returns The template with every token replaced\n *\n * @example\n * ```ts\n * import { interpolateMessage } from '@orkestrel/qualifier'\n *\n * interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'\n * interpolateMessage('Missing {{gone}}', {}) // 'Missing '\n * ```\n */\nexport function interpolateMessage(\n\ttemplate: string,\n\tsubject: Readonly<Record<string, unknown>>,\n): string {\n\treturn template.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_match, path: string) => {\n\t\tconst value = resolveField(subject, path.split('.'))\n\t\tif (value === undefined) return ''\n\t\tif (isFiniteNumber(value)) return value.toLocaleString('en-US')\n\t\treturn String(value)\n\t})\n}\n\n/**\n * Renders a {@link Premise} comparison as a display-neutral verb phrase.\n *\n * @param comparison - The comparison to describe\n * @returns A display-neutral phrase\n *\n * @example\n * ```ts\n * import { renderComparison } from '@orkestrel/qualifier'\n *\n * renderComparison('above') // 'is more than'\n * ```\n */\nexport function renderComparison(comparison: NonNullable<Premise['comparison']>): string {\n\tswitch (comparison) {\n\t\tcase 'equals':\n\t\t\treturn 'is'\n\t\tcase 'not':\n\t\t\treturn 'is not'\n\t\tcase 'above':\n\t\t\treturn 'is more than'\n\t\tcase 'below':\n\t\t\treturn 'is less than'\n\t\tcase 'from':\n\t\t\treturn 'is at least'\n\t\tcase 'to':\n\t\t\treturn 'is at most'\n\t\tcase 'any':\n\t\t\treturn 'is any of'\n\t\tcase 'none':\n\t\t\treturn 'is none of'\n\t\tcase 'between':\n\t\t\treturn 'is between'\n\t\tcase 'outside':\n\t\t\treturn 'is outside'\n\t}\n}\n\n/**\n * Renders a structured or scalar expected value display-neutrally.\n *\n * @remarks\n * An array renders as its elements, each `String`-coerced, joined with `', '`.\n * A reason `Bounds`-shaped record (the `between` / `outside` expected value)\n * renders its present sides joined with the word `and`; an absent side is\n * omitted. A finite number and everything else fall back to plain `String()` —\n * a finite number is not grouped here (grouping is {@link interpolateMessage}'s\n * message-token-only behavior).\n *\n * @param value - The value to render\n * @returns A display-neutral rendering\n *\n * @example\n * ```ts\n * import { renderValue } from '@orkestrel/qualifier'\n *\n * renderValue([18, 25, 40]) // '18, 25, 40'\n * renderValue({ minimum: 18, maximum: 65 }) // '18 and 65'\n * renderValue(42) // '42'\n * ```\n */\nexport function renderValue(value: unknown): string {\n\tif (isArray(value)) return value.map((entry) => String(entry)).join(', ')\n\tif (isBounds(value)) {\n\t\tconst sides: string[] = []\n\t\tif (value.minimum !== undefined) sides.push(String(value.minimum))\n\t\tif (value.maximum !== undefined) sides.push(String(value.maximum))\n\t\treturn sides.join(' and ')\n\t}\n\treturn String(value)\n}\n\n/**\n * Renders one {@link Premise} into a display-neutral sentence.\n *\n * @remarks\n * The checked form renders only when `field` and `comparison` are both\n * present, and `description` then goes unused; a premise missing either half\n * of the checked pair renders as described instead.\n *\n * @param entry - The premise to render\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A display-neutral sentence\n *\n * @example\n * ```ts\n * import { renderPremise } from '@orkestrel/qualifier'\n *\n * renderPremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })\n * // 'age is more than 18 → met'\n *\n * renderPremise({ description: 'Applicant is enrolled', met: true })\n * // 'Applicant is enrolled → met'\n * ```\n */\nexport function renderPremise(entry: Premise, labels?: Readonly<Record<string, string>>): string {\n\tconst status = entry.met === undefined ? 'unknown' : entry.met ? 'met' : 'not met'\n\tif (entry.field === undefined || entry.comparison === undefined) {\n\t\treturn `${entry.description ?? 'Premise'} → ${status}`\n\t}\n\tconst field = formatField(entry.field)\n\tconst label = labels?.[field] ?? entry.label ?? field\n\tconst expected = entry.expected === undefined ? '' : ` ${renderValue(entry.expected)}`\n\treturn `${label} ${renderComparison(entry.comparison)}${expected} → ${status}`\n}\n\n/**\n * Builds a {@link Premise} from an authored {@link Check} and its evaluated\n * `CheckResult`.\n *\n * @param check - The authored check\n * @param result - The evaluated check result carrying the resolved `actual` and `met`\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh premise\n *\n * @example\n * ```ts\n * import { checkToPremise } from '@orkestrel/qualifier'\n * import { createCheck } from '@orkestrel/reason'\n *\n * const authored = createCheck('age', 'above', 18)\n * checkToPremise(authored, { field: 'age', actual: 25, met: true })\n * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }\n * ```\n */\nexport function checkToPremise(\n\tcheck: Check,\n\tresult: CheckResult,\n\tlabels?: Readonly<Record<string, string>>,\n): Premise {\n\tconst field = formatField(check.field)\n\treturn {\n\t\tfield: check.field,\n\t\t...(labels?.[field] === undefined ? {} : { label: labels[field] }),\n\t\tcomparison: check.operator,\n\t\texpected: check.value,\n\t\tactual: result.actual,\n\t\tmet: result.met,\n\t}\n}\n\n/**\n * Builds rich premises for one fired {@link Rule} by walking its premise atoms\n * and re-evaluating each against the working subject.\n *\n * @remarks\n * A reason rule result carries only booleans, so this is the qualifier's own\n * premise-audit projection: each authored premise expression is flattened to its\n * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through\n * the injected `evaluator`. A membership check (`any` / `none`) over an empty\n * array value is content-free and is skipped.\n *\n * @param rule - The authored rule\n * @param working - The working subject to evaluate against\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh, ordered list of rich premises\n *\n * @example\n * ```ts\n * import { ruleToPremises } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const evaluator = createEvaluator()\n * const premises = ruleToPremises(gates.rules[0], { licensed: false }, evaluator)\n *\n * premises[0]?.met // true\n * ```\n */\nexport function ruleToPremises(\n\trule: Rule,\n\tworking: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): readonly Premise[] {\n\tconst output: Premise[] = []\n\tfor (const premise of rule.premises) {\n\t\tfor (const atom of extractAtoms(premise)) {\n\t\t\tconst { check } = atom\n\t\t\tif (\n\t\t\t\t(check.operator === 'any' || check.operator === 'none') &&\n\t\t\t\tisArray(check.value) &&\n\t\t\t\tcheck.value.length === 0\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutput.push(checkToPremise(check, evaluator.evaluate(check, working), labels))\n\t\t}\n\t}\n\treturn output\n}\n\n/**\n * Locates an authored {@link Rule} by id.\n *\n * @param definition - The logical definition to search\n * @param id - The rule id\n * @returns The matching rule, or `undefined`\n *\n * @example\n * ```ts\n * import { findRule } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n *\n * findRule(gates, 'licensed')?.id // 'licensed'\n * ```\n */\nexport function findRule(definition: LogicalDefinition, id: string): Rule | undefined {\n\treturn definition.rules.find((rule) => rule.id === id)\n}\n\n/**\n * Projects one reason result into the internal qualification namespace.\n *\n * @remarks\n * A quantitative result contributes its numeric `value`. A logical result\n * contributes a record of its `conclusion` plus every applied rule's asserted\n * conclusion fields (`extractConclusions`). A mismatched pairing contributes\n * `false`. The projection is pure — it never mutates the subject or the result.\n *\n * @param definition - The pass definition that produced the result\n * @param result - The evaluated reason result\n * @returns The pass's working projection\n *\n * @example\n * ```ts\n * import { reasonResultToProjection } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const result = {\n * reasoning: 'quantitative' as const,\n * value: 500_000,\n * groups: [],\n * count: 1,\n * success: true,\n * trace: [],\n * errors: [],\n * }\n * reasonResultToProjection(cap, result) // 500000\n * ```\n */\nexport function reasonResultToProjection(\n\tdefinition: QualificationPass,\n\tresult: ReasonResult,\n): QualificationProjection {\n\tif (definition.reasoning === 'quantitative' && result.reasoning === 'quantitative') {\n\t\treturn result.value\n\t}\n\tif (definition.reasoning !== 'logical' || result.reasoning !== 'logical') return false\n\tconst projection: Record<string, unknown> = { conclusion: result.conclusion }\n\tfor (const resolved of result.rules) {\n\t\tif (!resolved.applied) continue\n\t\tconst authored = definition.rules.find((rule) => rule.id === resolved.id)\n\t\tif (authored === undefined) continue\n\t\tconst conclusions = extractConclusions(authored.conclusion)\n\t\tfor (const [key, value] of Object.entries(conclusions)) projection[key] = value\n\t}\n\treturn projection\n}\n\n/**\n * Projects a quantitative result into a {@link Derivation} audit record.\n *\n * @param id - The pass id\n * @param result - The evaluated quantitative result\n * @returns A fresh derivation\n *\n * @example\n * ```ts\n * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'\n *\n * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }\n * quantitativeResultToDerivation('cap', result)\n * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }\n * ```\n */\nexport function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation {\n\treturn {\n\t\tid,\n\t\tvalue: result.value,\n\t\tsuccess: result.success,\n\t\ttrace: [...result.trace],\n\t\terrors: [...result.errors],\n\t}\n}\n\n/**\n * Wraps a {@link QualificationContext} under {@link QUALIFICATION_KEY}.\n *\n * @param context - The accumulated projection context\n * @returns A record carrying the context under the reserved key\n *\n * @example\n * ```ts\n * import { qualificationToRecord } from '@orkestrel/qualifier'\n *\n * qualificationToRecord({ cap: 500_000 }) // { qualification: { cap: 500000 } }\n * ```\n */\nexport function qualificationToRecord(\n\tcontext: QualificationContext,\n): Readonly<Record<string, QualificationContext>> {\n\treturn { [QUALIFICATION_KEY]: context }\n}\n\n/**\n * Merges one pass projection into the context, copy-on-write.\n *\n * @param context - The current projection context\n * @param id - The pass id to project under\n * @param projection - The pass's working projection\n * @returns A fresh context with the projection added\n *\n * @example\n * ```ts\n * import { mergeQualificationContext } from '@orkestrel/qualifier'\n *\n * mergeQualificationContext({ cap: 500_000 }, 'excess', 250_000)\n * // { cap: 500000, excess: 250000 }\n * ```\n */\nexport function mergeQualificationContext(\n\tcontext: QualificationContext,\n\tid: string,\n\tprojection: QualificationProjection,\n): QualificationContext {\n\treturn { ...context, [id]: projection }\n}\n\n/**\n * Joins a ruling, its logical rule result, the pass, the pre-projection subject,\n * and an evaluator into a {@link Finding}.\n *\n * @remarks\n * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated\n * against the same subject snapshot the pass evaluated, so a conclusion the pass\n * derived can never satisfy its own reported premise. The message interpolates\n * against that snapshot.\n *\n * @param ruling - The authored ruling\n * @param pass - The pass the ruling's rule lives in\n * @param result - The pass's evaluated logical result\n * @param subject - The pre-projection subject snapshot the pass evaluated\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh finding\n *\n * @example\n * ```ts\n * import { rulingToFinding } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }\n * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true] }], count: 1, success: true, trace: [], errors: [] }\n * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())\n *\n * finding.applied // true\n * ```\n */\nexport function rulingToFinding(\n\truling: Ruling,\n\tpass: QualificationPass,\n\tresult: LogicalResult,\n\tsubject: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): Finding {\n\tconst entry = result.rules.find((resolved) => resolved.id === ruling.rule)\n\tconst applied = entry?.applied ?? false\n\tconst rule = pass.reasoning === 'logical' ? findRule(pass, ruling.rule) : undefined\n\tconst premises = rule === undefined ? [] : ruleToPremises(rule, subject, evaluator, labels)\n\treturn {\n\t\tid: ruling.id,\n\t\tpass: ruling.pass,\n\t\trule: ruling.rule,\n\t\teffect: ruling.effect,\n\t\t...(ruling.scope === undefined ? {} : { scope: ruling.scope }),\n\t\tapplied,\n\t\t...(ruling.message === undefined\n\t\t\t? {}\n\t\t\t: { message: interpolateMessage(ruling.message, subject) }),\n\t\tpremises,\n\t}\n}\n\n/**\n * Derives global eligibility from applied, unscoped findings.\n *\n * @remarks\n * Only findings that both applied and carry no `scope` shape global eligibility.\n * A failed pass contributes a synthetic `referral` (fail-closed), passed through\n * `failed`.\n *\n * @param findings - The resolved findings\n * @param failed - If `true`, adds a synthetic `referral` impact so an operational failure\n * fails closed; if `false`, derives eligibility from the findings alone. Default: `false`\n * @returns The most severe global eligibility\n *\n * @example\n * ```ts\n * import { deriveFindingEligibility } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const, applied: true, premises: [] }\n * deriveFindingEligibility([finding]) // 'ineligible'\n * ```\n */\nexport function deriveFindingEligibility(\n\tfindings: readonly Finding[],\n\tfailed = false,\n): Eligibility {\n\tconst eligibilities = findings\n\t\t.filter((finding) => finding.applied && finding.scope === undefined)\n\t\t.map((finding) => EFFECT_ELIGIBILITIES[finding.effect])\n\tif (failed) eligibilities.push('referral')\n\treturn combineEligibilities(eligibilities)\n}\n\n/**\n * Returns the most severe {@link Eligibility} in a list.\n *\n * @param eligibilities - The eligibilities to combine\n * @returns The most severe eligibility, or `'eligible'` for an empty list\n *\n * @example\n * ```ts\n * import { combineEligibilities } from '@orkestrel/qualifier'\n *\n * combineEligibilities(['eligible', 'referral']) // 'referral'\n * ```\n */\nexport function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility {\n\tfor (const eligibility of ELIGIBILITY_PRECEDENCE) {\n\t\tif (eligibilities.includes(eligibility)) return eligibility\n\t}\n\treturn 'eligible'\n}\n\n/**\n * Derives one eligibility per finding scope.\n *\n * @remarks\n * Only applied, scoped findings contribute. Each named scope combines its own\n * findings' impacts by severity. An unmentioned scope is absent (eligible by\n * default at the program layer).\n *\n * @param findings - The resolved findings\n * @returns A fresh record of scope to its combined eligibility\n *\n * @example\n * ```ts\n * import { deriveScopeEligibilities } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'coastal', pass: 'wind', rule: 'coastal', effect: 'restriction' as const, scope: 'wind', applied: true, premises: [] }\n * deriveScopeEligibilities([finding]) // { wind: 'ineligible' }\n * ```\n */\nexport function deriveScopeEligibilities(\n\tfindings: readonly Finding[],\n): Readonly<Record<string, Eligibility>> {\n\tconst grouped: Record<string, Eligibility[]> = {}\n\tfor (const finding of findings) {\n\t\tif (!finding.applied || finding.scope === undefined) continue\n\t\tconst current = grouped[finding.scope] ?? []\n\t\tgrouped[finding.scope] = [...current, EFFECT_ELIGIBILITIES[finding.effect]]\n\t}\n\tconst scopes: Record<string, Eligibility> = {}\n\tfor (const [scope, eligibilities] of Object.entries(grouped)) {\n\t\tscopes[scope] = combineEligibilities(eligibilities)\n\t}\n\treturn scopes\n}\n\n/**\n * Describes each ruling whose pass or rule does not exist or whose pass is not\n * logical, and each pass id shadowing the reserved {@link QUALIFICATION_KEY}.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of reference-error messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, createRuling, describeMissingReferences } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates], {\n * rulings: [createRuling('license', 'gates', 'absent', 'restriction')],\n * })\n *\n * describeMissingReferences(definition)\n * // [\"Ruling 'license' references missing rule 'absent' in pass 'gates'\"]\n * ```\n */\nexport function describeMissingReferences(definition: QualificationDefinition): readonly string[] {\n\tconst errors: string[] = []\n\tconst passes = new Map(definition.passes.map((pass) => [pass.id, pass]))\n\tfor (const pass of definition.passes) {\n\t\tif (pass.id === QUALIFICATION_KEY) {\n\t\t\terrors.push(`Pass id must not equal reserved key '${QUALIFICATION_KEY}'`)\n\t\t}\n\t}\n\tfor (const ruling of definition.rulings ?? []) {\n\t\tconst pass = passes.get(ruling.pass)\n\t\tif (pass === undefined) {\n\t\t\terrors.push(`Ruling '${ruling.id}' references missing pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (pass.reasoning !== 'logical') {\n\t\t\terrors.push(`Ruling '${ruling.id}' references non-logical pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (findRule(pass, ruling.rule) === undefined) {\n\t\t\terrors.push(\n\t\t\t\t`Ruling '${ruling.id}' references missing rule '${ruling.rule}' in pass '${ruling.pass}'`,\n\t\t\t)\n\t\t}\n\t}\n\treturn errors\n}\n\n/**\n * Determines whether a subject already owns the reserved {@link QUALIFICATION_KEY}.\n *\n * @param subject - The subject to check\n * @returns True if the subject owns the reserved key; false otherwise\n *\n * @example\n * ```ts\n * import { hasReservedKey } from '@orkestrel/qualifier'\n *\n * hasReservedKey({ id: 's1', qualification: {} }) // true\n * ```\n */\nexport function hasReservedKey(subject: Subject): boolean {\n\treturn Object.hasOwn(subject, QUALIFICATION_KEY)\n}\n\n/**\n * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.\n *\n * @param value - The candidate subject to validate\n * @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or\n * when it already carries the reserved `qualification` key\n *\n * @example\n * ```ts\n * import { assertSubject } from '@orkestrel/qualifier'\n *\n * assertSubject({ id: 's1' }) // narrows to Subject\n * ```\n */\nexport function assertSubject(value: unknown): asserts value is Subject {\n\tif (!isRecord(value)) {\n\t\tthrow new QualifierError('MISMATCH', 'Qualification subject must be a record')\n\t}\n\tif (hasReservedKey(value)) {\n\t\tthrow new QualifierError(\n\t\t\t'MISMATCH',\n\t\t\t`Qualification subject must not contain reserved key '${QUALIFICATION_KEY}'`,\n\t\t)\n\t}\n}\n\n/**\n * Maps an engine throw caught while running one pass to a typed {@link QualifierError}.\n *\n * @remarks\n * A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the\n * pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;\n * every other `ReasonError` code (for example `MISSING`) and every non-`ReasonError` throw\n * (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw\n * is preserved as `context.cause`.\n *\n * @param error - The value caught from the engine's `reason` call\n * @param pass - The id of the pass that was running\n * @returns A fresh, typed {@link QualifierError}\n *\n * @example\n * ```ts\n * import { mapEngineError } from '@orkestrel/qualifier'\n * import { ReasonError } from '@orkestrel/reason'\n *\n * mapEngineError(new ReasonError('MISSING', 'No reasoner registered'), 'cap').code // 'ENGINE'\n * ```\n */\nexport function mapEngineError(error: unknown, pass: string): QualifierError {\n\tif (isReasonError(error)) {\n\t\tif (error.code === 'INVALID') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DEFINITION',\n\t\t\t\t`Pass '${pass}' failed engine validation: ${error.message}`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\tif (error.code === 'DESTROYED') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DESTROYED',\n\t\t\t\t`Qualifier engine was destroyed during pass '${pass}'`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${error.message}`, {\n\t\t\tpass,\n\t\t\tcause: error,\n\t\t})\n\t}\n\tconst message = isError(error) ? error.message : String(error)\n\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${message}`, {\n\t\tpass,\n\t\tcause: error,\n\t})\n}\n\n/**\n * Describes each logical pass carrying no rulings.\n *\n * @remarks\n * A logical pass with no matching ruling can never contribute a finding, so its\n * rules only ever influence later passes' reads — this is very likely an authoring\n * oversight.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeEmptyLogicalPasses } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates])\n *\n * describeEmptyLogicalPasses(definition) // [\"Logical pass 'gates' has no rulings\"]\n * ```\n */\nexport function describeEmptyLogicalPasses(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tfor (const pass of definition.passes) {\n\t\tif (pass.reasoning !== 'logical') continue\n\t\tconst hasRuling = (definition.rulings ?? []).some((ruling) => ruling.pass === pass.id)\n\t\tif (!hasRuling) warnings.push(`Logical pass '${pass.id}' has no rulings`)\n\t}\n\treturn warnings\n}\n\n/**\n * Describes each quantitative pass never read by a later pass.\n *\n * @remarks\n * A quantitative pass's projected value lives under `qualification.<id>` — if no\n * later logical premise or quantitative factor field reads that key, the\n * derivation is dead weight. Reads are collected per later pass: a logical pass's\n * premise atoms (conclusion atoms are writes, not reads), and a quantitative\n * pass's field/lookup/range factor sources plus factor checks.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeUnreadDerivations } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [cap])\n *\n * describeUnreadDerivations(definition) // [\"Quantitative pass 'cap' is never read by a later pass\"]\n * ```\n */\nexport function describeUnreadDerivations(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tdefinition.passes.forEach((pass, index) => {\n\t\tif (pass.reasoning !== 'quantitative') return\n\t\tconst key = formatField(['qualification', pass.id])\n\t\tconst reads = new Set<string>()\n\t\tfor (const later of definition.passes.slice(index + 1)) {\n\t\t\tif (later.reasoning === 'logical') {\n\t\t\t\tfor (const rule of later.rules) {\n\t\t\t\t\tfor (const premise of rule.premises) {\n\t\t\t\t\t\tfor (const atom of extractAtoms(premise)) reads.add(formatField(atom.check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (later.reasoning === 'quantitative') {\n\t\t\t\tfor (const group of later.groups) {\n\t\t\t\t\tfor (const factor of group.factors) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tfactor.source.origin === 'field' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'lookup' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'range'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treads.add(formatField(factor.source.field))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const check of factor.checks ?? []) reads.add(formatField(check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!reads.has(key)) {\n\t\t\twarnings.push(`Quantitative pass '${pass.id}' is never read by a later pass`)\n\t\t}\n\t})\n\treturn warnings\n}\n","import type {\n\tDerivation,\n\tFinding,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationResult,\n\tQualifierEventMap,\n\tQualifierInterface,\n\tQualifierOptions,\n} from './types.js'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tEvaluatorInterface,\n\tReasonInterface,\n\tReasonResult,\n\tReasonValidationResult,\n\tSubject,\n} from '@orkestrel/reason'\nimport { DEFAULT_QUALIFIER_VALIDATE } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport {\n\tassertSubject,\n\tdescribeEmptyLogicalPasses,\n\tdescribeMissingReferences,\n\tdescribeUnreadDerivations,\n\tderiveFindingEligibility,\n\tderiveScopeEligibilities,\n\tmapEngineError,\n\tmergeQualificationContext,\n\tqualificationToRecord,\n\tquantitativeResultToDerivation,\n\treasonResultToProjection,\n\trulingToFinding,\n} from './helpers.js'\nimport { isQualificationPass, isRuling } from './validators.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcreateEvaluator,\n\tcreateLogicalReasoner,\n\tcreateQuantitativeReasoner,\n\tcreateReason,\n\tfindDuplicates,\n} from '@orkestrel/reason'\n\n/**\n * Runs ordered passes over one reason engine and returns eligibility.\n *\n * @remarks\n * The engine is owned when self-created (destroyed on `destroy()`) and borrowed\n * when injected (never destroyed). Semantic validation runs before the first pass\n * whenever the `validate` option is on. `qualify` builds its working subject through\n * copy-on-write overlays only — the caller's `subject` is never mutated, and the\n * qualifier's internal projection is discarded after each call.\n */\nexport class Qualifier implements QualifierInterface {\n\treadonly #emitter: Emitter<QualifierEventMap>\n\treadonly #engine: ReasonInterface\n\treadonly #evaluator: EvaluatorInterface\n\treadonly #owned: boolean\n\treadonly #validate: boolean\n\treadonly #labels: Readonly<Record<string, string>> | undefined\n\t#destroyed = false\n\n\tconstructor(options?: QualifierOptions) {\n\t\tthis.#emitter = new Emitter({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\tthis.#evaluator = createEvaluator()\n\t\tthis.#owned = options?.engine === undefined\n\t\tthis.#engine =\n\t\t\toptions?.engine ??\n\t\t\tcreateReason({\n\t\t\t\treasoners: [\n\t\t\t\t\tcreateQuantitativeReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t\tcreateLogicalReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t],\n\t\t\t\tbail: false,\n\t\t\t})\n\t\tthis.#validate = options?.validate ?? DEFAULT_QUALIFIER_VALIDATE\n\t\tthis.#labels = options?.labels\n\t}\n\n\t/**\n\t * Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and\n\t * `destroy`.\n\t *\n\t * @returns The emitter this qualifier owns\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.emitter.on('qualify', (result) => console.log(result.eligibility))\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tget emitter(): EmitterInterface<QualifierEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/**\n\t * Qualifies one subject against one authored definition.\n\t *\n\t * @remarks\n\t * Semantic validation runs first when the `validate` option is on, so an invalid\n\t * definition throws before any pass runs. Passes run in authored order over a\n\t * copy-on-write working subject, and the caller's `subject` is never mutated. An\n\t * unscoped restriction stops the remaining passes because no later finding can be\n\t * more severe.\n\t *\n\t * @param subject - The record to qualify\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh, frozen qualification result\n\t * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the\n\t * definition fails semantic validation, or when the engine rejects a pass as an invalid\n\t * definition; `'MISMATCH'` when the subject is not a record or already carries the\n\t * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine\n\t * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, createRuling, Qualifier } from '@orkestrel/qualifier'\n\t * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n\t *\n\t * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n\t * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n\t * ])\n\t * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {\n\t * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],\n\t * })\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tqualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tthis.#alive()\n\t\tif (this.#validate) {\n\t\t\tconst validation = this.validate(definition)\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new QualifierError(\n\t\t\t\t\t'DEFINITION',\n\t\t\t\t\t`Qualification definition is invalid: ${validation.errors.join(', ')}`,\n\t\t\t\t\t{ definition: definition.id },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn this.#qualify(subject, definition)\n\t}\n\n\t/**\n\t * Validates one authored definition semantically, without running it.\n\t *\n\t * @remarks\n\t * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:\n\t * non-empty id and name, valid and uniquely identified passes and rulings, every\n\t * ruling reference resolving to a logical pass and one of its rules, and no pass id\n\t * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no\n\t * ruling, and a derivation no later pass reads are warnings rather than errors.\n\t *\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`\n\t * @throws {@link QualifierError} `'DESTROYED'` after `destroy`\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))\n\t * // { valid: true, errors: [], warnings: ['Definition has no passes'] }\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tvalidate(definition: QualificationDefinition): ReasonValidationResult {\n\t\tthis.#alive()\n\t\tconst errors: string[] = []\n\t\tconst warnings: string[] = []\n\t\tif (definition.id.length === 0) errors.push('Definition id must not be empty')\n\t\tif (definition.name.length === 0) errors.push('Definition name must not be empty')\n\t\tdefinition.passes.forEach((pass, index) => {\n\t\t\tif (!isQualificationPass(pass)) {\n\t\t\t\terrors.push(`Pass at index ${index} is not a valid qualification pass`)\n\t\t\t}\n\t\t})\n\t\t;(definition.rulings ?? []).forEach((ruling, index) => {\n\t\t\tif (!isRuling(ruling)) errors.push(`Ruling at index ${index} is not a valid ruling`)\n\t\t})\n\t\tfor (const id of findDuplicates(definition.passes)) errors.push(`Duplicate pass id '${id}'`)\n\t\tfor (const id of findDuplicates(definition.rulings ?? [])) {\n\t\t\terrors.push(`Duplicate ruling id '${id}'`)\n\t\t}\n\t\tfor (const reference of describeMissingReferences(definition)) errors.push(reference)\n\t\tif (definition.passes.length === 0) warnings.push('Definition has no passes')\n\t\tfor (const warning of describeEmptyLogicalPasses(definition)) warnings.push(warning)\n\t\tfor (const warning of describeUnreadDerivations(definition)) warnings.push(warning)\n\t\treturn { valid: errors.length === 0, errors, warnings }\n\t}\n\n\t/**\n\t * Destroys this qualifier, idempotently.\n\t *\n\t * @remarks\n\t * An engine this qualifier created is destroyed; an injected engine stays\n\t * caller-owned and is left alone. The `destroy` event fires before the emitter\n\t * itself is destroyed, and a later call returns without re-emitting.\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.destroy()\n\t * qualifier.destroy() // idempotent\n\t * ```\n\t */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tif (this.#owned) this.#engine.destroy()\n\t\tthis.#destroyed = true\n\t\tthis.#emitter.emit('destroy')\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#qualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tassertSubject(subject)\n\t\tlet context: QualificationContext = {}\n\t\tlet working: Subject = { ...subject }\n\t\tconst findings: Finding[] = []\n\t\tconst derivations: Derivation[] = []\n\t\tconst trace: string[] = []\n\t\tconst errors: string[] = []\n\t\tlet failed = false\n\n\t\tfor (const pass of definition.passes) {\n\t\t\tthis.#alive()\n\t\t\tconst evaluated = working\n\t\t\tlet result: ReasonResult\n\t\t\ttry {\n\t\t\t\tresult = this.#engine.reason(evaluated, pass)\n\t\t\t} catch (error) {\n\t\t\t\tthrow mapEngineError(error, pass.id)\n\t\t\t}\n\n\t\t\ttrace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`))\n\t\t\terrors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`))\n\n\t\t\tif (result.reasoning === 'quantitative') {\n\t\t\t\tconst derivation = quantitativeResultToDerivation(pass.id, result)\n\t\t\t\tderivations.push(derivation)\n\t\t\t\tthis.#emitter.emit('derive', derivation)\n\t\t\t}\n\n\t\t\tif (result.reasoning === 'logical') {\n\t\t\t\tconst rulings = (definition.rulings ?? []).filter((ruling) => ruling.pass === pass.id)\n\t\t\t\tfor (const ruling of rulings) {\n\t\t\t\t\tconst finding = rulingToFinding(\n\t\t\t\t\t\truling,\n\t\t\t\t\t\tpass,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tevaluated,\n\t\t\t\t\t\tthis.#evaluator,\n\t\t\t\t\t\tthis.#labels,\n\t\t\t\t\t)\n\t\t\t\t\tfindings.push(finding)\n\t\t\t\t\tthis.#emitter.emit('finding', finding)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst projection = reasonResultToProjection(pass, result)\n\t\t\tcontext = mergeQualificationContext(context, pass.id, projection)\n\t\t\tworking = { ...subject, ...qualificationToRecord(context) }\n\n\t\t\tif (!result.success) {\n\t\t\t\tfailed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif (deriveFindingEligibility(findings) === 'ineligible') break\n\t\t}\n\n\t\tconst success = !failed && errors.length === 0\n\t\tconst scopes = deriveScopeEligibilities(findings)\n\t\tconst qualification: QualificationResult = Object.freeze({\n\t\t\tid: definition.id,\n\t\t\tname: definition.name,\n\t\t\teligibility: deriveFindingEligibility(findings, !success),\n\t\t\tscopes: Object.freeze(scopes),\n\t\t\tfindings: Object.freeze(findings),\n\t\t\tderivations: Object.freeze(derivations),\n\t\t\tsuccess,\n\t\t\ttrace: Object.freeze(trace),\n\t\t\terrors: Object.freeze(errors),\n\t\t})\n\t\tthis.#emitter.emit('qualify', qualification)\n\t\treturn qualification\n\t}\n\n\t#alive(): void {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new QualifierError('DESTROYED', 'Qualifier has been destroyed')\n\t\t}\n\t}\n}\n","import type {\n\tQualificationDefinition,\n\tQualificationEffect,\n\tQualificationInput,\n\tQualificationPass,\n\tQualifierInterface,\n\tQualifierOptions,\n\tRuling,\n\tRulingInput,\n} from './types.js'\nimport { Qualifier } from './Qualifier.js'\n\n/**\n * Creates one {@link QualifierInterface} over a reason engine.\n *\n * @remarks\n * A standalone qualifier creates and owns one shared quantitative-plus-logical\n * reason engine, destroying it on `destroy()`. An injected `options.engine`\n * remains caller-owned and is never destroyed. `validate` runs semantic\n * validation before every qualification. Default: `true`. `labels` overrides the\n * display name of a premise field, keyed by the dot-joined field path\n * `formatField` produces, and takes precedence over a premise's own `label`.\n *\n * @param options - Optional injected engine, validation, labels, and emitter hooks\n * @returns A {@link QualifierInterface}\n *\n * @example Create a qualifier\n * ```ts\n * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n * \tcreateRule(\n * \t\t'licensed',\n * \t\t[createAtom('licensed', 'equals', false)],\n * \t\tcreateAtom('blocked', 'equals', true),\n * \t),\n * ])\n *\n * const bare = createRuling('license', 'gates', 'licensed', 'restriction')\n * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {\n * \tmessage: 'A license is required',\n * })\n *\n * 'message' in bare // false — an absent optional key is omitted, never written as undefined\n * messaged.message // 'A license is required'\n *\n * const passes = [gates]\n * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {\n * \trulings: [messaged],\n * })\n *\n * 'description' in definition // false\n * definition.passes === passes // false — the factory copies what it is handed\n *\n * const qualifier = createQualifier()\n * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)\n * qualifier.destroy()\n * ```\n */\nexport function createQualifier(options?: QualifierOptions): QualifierInterface {\n\treturn new Qualifier(options)\n}\n\n/**\n * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.\n *\n * @remarks\n * `passes` and `rulings` arrays are copied, and record `metadata` is\n * shallow-copied so nested values are not deep-cloned.\n *\n * @param id - The definition id\n * @param name - The display name\n * @param passes - The ordered qualification passes\n * @param input - Optional description, rulings, and metadata\n * @returns A fresh qualification definition\n *\n * @example\n * ```ts\n * import { createQualificationDefinition } from '@orkestrel/qualifier'\n *\n * createQualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })\n * ```\n */\nexport function createQualificationDefinition(\n\tid: string,\n\tname: string,\n\tpasses: readonly QualificationPass[],\n\tinput?: QualificationInput,\n): QualificationDefinition {\n\treturn {\n\t\tid,\n\t\tname,\n\t\tpasses: [...passes],\n\t\t...(input?.description === undefined ? {} : { description: input.description }),\n\t\t...(input?.rulings === undefined ? {} : { rulings: [...input.rulings] }),\n\t\t...(input?.metadata === undefined ? {} : { metadata: { ...input.metadata } }),\n\t}\n}\n\n/**\n * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.\n *\n * @param id - The ruling id\n * @param pass - The logical pass id the rule lives in\n * @param rule - The rule id whose firing this ruling reacts to\n * @param effect - The eligibility impact when the rule fires\n * @param input - Optional selection scope and message template\n * @returns A fresh ruling\n *\n * @example\n * ```ts\n * import { createRuling } from '@orkestrel/qualifier'\n *\n * createRuling('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })\n * ```\n */\nexport function createRuling(\n\tid: string,\n\tpass: string,\n\trule: string,\n\teffect: QualificationEffect,\n\tinput?: RulingInput,\n): Ruling {\n\treturn {\n\t\tid,\n\t\tpass,\n\t\trule,\n\t\teffect,\n\t\t...(input?.scope === undefined ? {} : { scope: input.scope }),\n\t\t...(input?.message === undefined ? {} : { message: input.message }),\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["holds","INTRINSICS","isObject","isFunction","literalOf","whereOf","objectOf","isFieldPath","isString","isComparison","isBoolean","arrayOf","isNumber","isQuantitativeDefinition","isLogicalDefinition","recordOf","isRecord","resolveField","isFiniteNumber","isArray","isBounds","formatField","extractAtoms","extractConclusions","isRecord","isReasonError","isError","Emitter","createEvaluator","createReason","createQuantitativeReasoner","createLogicalReasoner","findDuplicates"],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Qualifier.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { Eligibility, QualificationEffect } from './types.js'\n\n/**\n * Holds the default definition validation policy for `createQualifier` and\n * `Qualifier.qualify`, `true`.\n */\nexport const DEFAULT_QUALIFIER_VALIDATE = true\n\n/**\n * Names `'qualification'`, the reserved internal projection namespace a pass's working\n * projection is written under.\n */\nexport const QUALIFICATION_KEY = 'qualification'\n\n/** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */\nexport const ELIGIBILITY_PRECEDENCE: readonly Eligibility[] = Object.freeze([\n\t'ineligible',\n\t'referral',\n\t'eligible',\n])\n\n/**\n * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to\n * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.\n */\nexport const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>> =\n\tObject.freeze({\n\t\trestriction: 'ineligible',\n\t\treferral: 'referral',\n\t\tcondition: 'eligible',\n\t})\n","import type { QualifierErrorCode, QualifierErrorContext } from './types.js'\nimport { holds, INTRINSICS, isFunction, isObject } from '@orkestrel/contract'\n\n/**\n * Represents a coded programmer error thrown by the qualifier layer, carrying a\n * `QualifierErrorCode` and an optional context record.\n *\n * @remarks\n * `DEFINITION` — a qualification definition failed semantic validation; when\n * `qualify`'s own validation raises it, `context.definition` names the\n * definition, and when an engine throw maps to it, `context.pass` names the pass\n * and `context.cause` preserves the original throw. `MISMATCH` — a subject is\n * not a record or already carries the reserved `qualification` key.\n * `DESTROYED` — use of a destroyed qualifier; an engine-mapped one carries\n * `context.pass` and `context.cause`. `ENGINE` — the underlying reason engine\n * threw while running a pass; `context.pass` names the pass and `context.cause`\n * preserves the original throw. `context` is absent for a throw that carries no\n * payload.\n */\nexport class QualifierError extends Error {\n\treadonly code: QualifierErrorCode\n\treadonly context: QualifierErrorContext | undefined\n\n\tconstructor(code: QualifierErrorCode, message: string, context?: QualifierErrorContext) {\n\t\tsuper(message)\n\t\tINTRINSICS.define(this, Symbol.for('@orkestrel/qualifier.QualifierError'), { value: this })\n\t\tthis.name = 'QualifierError'\n\t\tthis.code = code\n\t\tthis.context = context\n\t}\n}\n\n/**\n * Narrows a caught value to a {@link QualifierError}.\n *\n * @param value - The caught value to test\n * @returns True if the error carries its own identity brand and declared members; false otherwise\n */\nexport function isQualifierError(value: unknown): value is QualifierError {\n\treturn holds(() => {\n\t\tif (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false\n\t\tconst brand = INTRINSICS.describe(value, Symbol.for('@orkestrel/qualifier.QualifierError'))\n\t\tif (brand?.value !== value) return false\n\t\tconst code: unknown = INTRINSICS.reflect.read(value, 'code')\n\t\tif (code !== 'DEFINITION' && code !== 'MISMATCH' && code !== 'DESTROYED' && code !== 'ENGINE')\n\t\t\treturn false\n\t\tconst context: unknown = INTRINSICS.reflect.read(value, 'context')\n\t\tif (context === undefined) return true\n\t\tif (!isObject(context) && !isFunction(context)) return false\n\t\tconst pass: unknown = INTRINSICS.reflect.read(context, 'pass')\n\t\tconst definition: unknown = INTRINSICS.reflect.read(context, 'definition')\n\t\treturn (\n\t\t\t(pass === undefined || typeof pass === 'string') &&\n\t\t\t(definition === undefined || typeof definition === 'string')\n\t\t)\n\t})\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tRuling,\n} from './types.js'\nimport {\n\tarrayOf,\n\tisBoolean,\n\tisNumber,\n\tisRecord,\n\tisString,\n\tliteralOf,\n\tobjectOf,\n\trecordOf,\n\twhereOf,\n} from '@orkestrel/contract'\nimport {\n\tisComparison,\n\tisFieldPath,\n\tisLogicalDefinition,\n\tisQuantitativeDefinition,\n} from '@orkestrel/reason'\n\n/**\n * Determines whether a value is an {@link Eligibility} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the eligibility literals; false otherwise\n */\nexport const isEligibility = literalOf('eligible', 'ineligible', 'referral')\n\n/**\n * Determines whether a value is a {@link QualificationEffect} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the effect literals; false otherwise\n */\nexport const isQualificationEffect = literalOf('restriction', 'referral', 'condition')\n\n/**\n * Determines whether a value is an open string-keyed record of {@link Eligibility} values.\n *\n * @remarks\n * Every own string-named value is checked through {@link isEligibility}, including\n * non-enumerable values. Inherited and symbol-named members are left unchecked because they\n * are outside the own string-keyed record this guard certifies.\n *\n * @param value - The value to test\n * @returns True if `value` is a non-array object whose own string-named values are\n * eligibilities; false otherwise\n */\nexport function isEligibilityRecord(\n\tvalue: unknown,\n): value is Readonly<Record<string, Eligibility>> {\n\treturn whereOf(objectOf({}), (record): record is Readonly<Record<string, Eligibility>> =>\n\t\tObject.getOwnPropertyNames(record).every((key) => isEligibility(Reflect.get(record, key))),\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Premise}.\n *\n * @remarks\n * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked\n * when they are defined. `expected` is unchecked and may be absent or contain any value\n * because its published type is `unknown`. `actual` is unchecked and may be absent or contain\n * any value for the same reason. Unknown members are admitted, because an open result admits\n * what the published contract does not name.\n *\n * @param value - The value to test\n * @returns True if every checked premise member follows its published type; false otherwise\n */\nexport function isPremise(value: unknown): value is Premise {\n\treturn objectOf(\n\t\t{\n\t\t\tfield: isFieldPath,\n\t\t\tlabel: isString,\n\t\t\tdescription: isString,\n\t\t\tcomparison: isComparison,\n\t\t\tmet: isBoolean,\n\t\t},\n\t\t['field', 'label', 'description', 'comparison', 'met'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Finding}.\n *\n * @remarks\n * Every published member is checked, including each nested {@link Premise}. Optional `scope`\n * and `message` members may be absent or `undefined`; a present defined value must be a\n * string. Unknown members are admitted, because a foreign result implementation may add them\n * without changing the published contract.\n *\n * @param value - The value to test\n * @returns True if every published finding member is valid; false otherwise\n */\nexport function isFinding(value: unknown): value is Finding {\n\treturn objectOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tapplied: isBoolean,\n\t\t\tmessage: isString,\n\t\t\tpremises: arrayOf(isPremise),\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Derivation}.\n *\n * @remarks\n * Every published member is checked: `value` follows the published `number` type, including\n * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are\n * admitted, because a foreign result implementation may add them without changing the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published derivation member is valid; false otherwise\n */\nexport function isDerivation(value: unknown): value is Derivation {\n\treturn objectOf({\n\t\tid: isString,\n\t\tvalue: isNumber,\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is an open {@link QualificationResult} returned by a qualifier.\n *\n * @remarks\n * This guard is result-postured for values returned through a borrowed qualifier interface. It\n * checks the complete published result closure — eligibility, scope values, findings,\n * derivations, success, and the audit trail — and admits unknown members and class instances,\n * because a borrowed `QualifierInterface` implementation may add them beyond the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published qualification-result member is valid; false otherwise\n */\nexport function isQualificationResult(value: unknown): value is QualificationResult {\n\treturn objectOf({\n\t\tid: isString,\n\t\tname: isString,\n\t\teligibility: isEligibility,\n\t\tscopes: isEligibilityRecord,\n\t\tfindings: arrayOf(isFinding),\n\t\tderivations: arrayOf(isDerivation),\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).\n *\n * @remarks\n * The guard is exact and requires either complete reason shape, the quantitative definition or\n * the logical one. Nothing is left unchecked, because the reason package owns both authored\n * input shapes.\n *\n * @param value - The value to test\n * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise\n */\nexport function isQualificationPass(value: unknown): value is QualificationPass {\n\treturn isQuantitativeDefinition(value) || isLogicalDefinition(value)\n}\n\n/**\n * Determines whether a value is an exact {@link Ruling} record.\n *\n * @remarks\n * Every authored ruling member and the complete key set are checked. Nothing is left\n * unchecked, because this package owns the authored input record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every ruling member and no unknown key; false otherwise\n */\nexport function isRuling(value: unknown): value is Ruling {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tmessage: isString,\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an exact {@link QualificationDefinition} record.\n *\n * @remarks\n * Every authored definition member, the nested pass and ruling shapes, and the complete key\n * set are checked. Nothing is left unchecked, because this package owns the authored input\n * record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every definition member and no unknown key; false otherwise\n */\nexport function isQualificationDefinition(value: unknown): value is QualificationDefinition {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tname: isString,\n\t\t\tdescription: isString,\n\t\t\tpasses: arrayOf(isQualificationPass),\n\t\t\trulings: arrayOf(isRuling),\n\t\t\tmetadata: isRecord,\n\t\t},\n\t\t['description', 'rulings', 'metadata'],\n\t)(value)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationProjection,\n\tRuling,\n} from './types.js'\nimport type {\n\tCheck,\n\tCheckResult,\n\tEvaluatorInterface,\n\tLogicalDefinition,\n\tLogicalResult,\n\tQuantitativeResult,\n\tReasonResult,\n\tRule,\n\tSubject,\n} from '@orkestrel/reason'\nimport { EFFECT_ELIGIBILITIES, ELIGIBILITY_PRECEDENCE, QUALIFICATION_KEY } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport { isArray, isError, isFiniteNumber, isRecord, resolveField } from '@orkestrel/contract'\nimport {\n\textractAtoms,\n\textractConclusions,\n\tformatField,\n\tisBounds,\n\tisReasonError,\n} from '@orkestrel/reason'\n\n/**\n * Interpolates `{{dotted.path}}` tokens in a message template against a subject.\n *\n * @remarks\n * Each token is split on `.` into a `FieldPath` array and resolved with\n * `resolveField` (a plain string field is one key, never dot-split — the split\n * here is the token-to-path bridge). A finite number renders with `en-US`\n * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.\n * An unresolved path renders as the empty string.\n *\n * @param template - The message template carrying `{{dotted.path}}` tokens\n * @param subject - The record tokens resolve against\n * @returns The template with every token replaced\n *\n * @example\n * ```ts\n * import { interpolateMessage } from '@orkestrel/qualifier'\n *\n * interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'\n * interpolateMessage('Missing {{gone}}', {}) // 'Missing '\n * ```\n */\nexport function interpolateMessage(\n\ttemplate: string,\n\tsubject: Readonly<Record<string, unknown>>,\n): string {\n\treturn template.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_match, path: string) => {\n\t\tconst value = resolveField(subject, path.split('.'))\n\t\tif (value === undefined) return ''\n\t\tif (isFiniteNumber(value)) return value.toLocaleString('en-US')\n\t\treturn String(value)\n\t})\n}\n\n/**\n * Renders a {@link Premise} comparison as a display-neutral verb phrase.\n *\n * @param comparison - The comparison to describe\n * @returns A display-neutral phrase\n *\n * @example\n * ```ts\n * import { renderComparison } from '@orkestrel/qualifier'\n *\n * renderComparison('above') // 'is more than'\n * ```\n */\nexport function renderComparison(comparison: NonNullable<Premise['comparison']>): string {\n\tswitch (comparison) {\n\t\tcase 'equals':\n\t\t\treturn 'is'\n\t\tcase 'not':\n\t\t\treturn 'is not'\n\t\tcase 'above':\n\t\t\treturn 'is more than'\n\t\tcase 'below':\n\t\t\treturn 'is less than'\n\t\tcase 'from':\n\t\t\treturn 'is at least'\n\t\tcase 'to':\n\t\t\treturn 'is at most'\n\t\tcase 'any':\n\t\t\treturn 'is any of'\n\t\tcase 'none':\n\t\t\treturn 'is none of'\n\t\tcase 'between':\n\t\t\treturn 'is between'\n\t\tcase 'outside':\n\t\t\treturn 'is outside'\n\t}\n}\n\n/**\n * Renders a structured or scalar expected value display-neutrally.\n *\n * @remarks\n * An array renders as its elements, each `String`-coerced, joined with `', '`.\n * A reason `Bounds`-shaped record (the `between` / `outside` expected value)\n * renders its present sides joined with the word `and`; an absent side is\n * omitted. A finite number and everything else fall back to plain `String()` —\n * a finite number is not grouped here (grouping is {@link interpolateMessage}'s\n * message-token-only behavior).\n *\n * @param value - The value to render\n * @returns A display-neutral rendering\n *\n * @example\n * ```ts\n * import { renderValue } from '@orkestrel/qualifier'\n *\n * renderValue([18, 25, 40]) // '18, 25, 40'\n * renderValue({ minimum: 18, maximum: 65 }) // '18 and 65'\n * renderValue(42) // '42'\n * ```\n */\nexport function renderValue(value: unknown): string {\n\tif (isArray(value)) return value.map((entry) => String(entry)).join(', ')\n\tif (isBounds(value)) {\n\t\tconst sides: string[] = []\n\t\tif (value.minimum !== undefined) sides.push(String(value.minimum))\n\t\tif (value.maximum !== undefined) sides.push(String(value.maximum))\n\t\treturn sides.join(' and ')\n\t}\n\treturn String(value)\n}\n\n/**\n * Renders one {@link Premise} into a display-neutral sentence.\n *\n * @remarks\n * The checked form renders only when `field` and `comparison` are both\n * present, and `description` then goes unused; a premise missing either half\n * of the checked pair renders as described instead.\n *\n * @param entry - The premise to render\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A display-neutral sentence\n *\n * @example\n * ```ts\n * import { renderPremise } from '@orkestrel/qualifier'\n *\n * renderPremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })\n * // 'age is more than 18 → met'\n *\n * renderPremise({ description: 'Applicant is enrolled', met: true })\n * // 'Applicant is enrolled → met'\n * ```\n */\nexport function renderPremise(entry: Premise, labels?: Readonly<Record<string, string>>): string {\n\tconst status = entry.met === undefined ? 'unknown' : entry.met ? 'met' : 'not met'\n\tif (entry.field === undefined || entry.comparison === undefined) {\n\t\treturn `${entry.description ?? 'Premise'} → ${status}`\n\t}\n\tconst field = formatField(entry.field)\n\tconst label = labels?.[field] ?? entry.label ?? field\n\tconst expected = entry.expected === undefined ? '' : ` ${renderValue(entry.expected)}`\n\treturn `${label} ${renderComparison(entry.comparison)}${expected} → ${status}`\n}\n\n/**\n * Builds a {@link Premise} from an authored {@link Check} and its evaluated\n * `CheckResult`.\n *\n * @param check - The authored check\n * @param result - The evaluated check result carrying the resolved `actual` and `met`\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh premise\n *\n * @example\n * ```ts\n * import { checkToPremise } from '@orkestrel/qualifier'\n * import { createCheck } from '@orkestrel/reason'\n *\n * const authored = createCheck('age', 'above', 18)\n * checkToPremise(authored, { field: 'age', actual: 25, met: true })\n * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }\n * ```\n */\nexport function checkToPremise(\n\tcheck: Check,\n\tresult: CheckResult,\n\tlabels?: Readonly<Record<string, string>>,\n): Premise {\n\tconst field = formatField(check.field)\n\treturn {\n\t\tfield: check.field,\n\t\t...(labels?.[field] === undefined ? {} : { label: labels[field] }),\n\t\tcomparison: check.operator,\n\t\texpected: check.value,\n\t\tactual: result.actual,\n\t\tmet: result.met,\n\t}\n}\n\n/**\n * Builds rich premises for one fired {@link Rule} by walking its premise atoms\n * and re-evaluating each against the working subject.\n *\n * @remarks\n * A reason rule result carries only booleans, so this is the qualifier's own\n * premise-audit projection: each authored premise expression is flattened to its\n * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through\n * the injected `evaluator`. A membership check (`any` / `none`) over an empty\n * array value is content-free and is skipped.\n *\n * @param rule - The authored rule\n * @param working - The working subject to evaluate against\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh, ordered list of rich premises\n *\n * @example\n * ```ts\n * import { ruleToPremises } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const evaluator = createEvaluator()\n * const premises = ruleToPremises(gates.rules[0], { licensed: false }, evaluator)\n *\n * premises[0]?.met // true\n * ```\n */\nexport function ruleToPremises(\n\trule: Rule,\n\tworking: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): readonly Premise[] {\n\tconst output: Premise[] = []\n\tfor (const premise of rule.premises) {\n\t\tfor (const atom of extractAtoms(premise)) {\n\t\t\tconst { check } = atom\n\t\t\tif (\n\t\t\t\t(check.operator === 'any' || check.operator === 'none') &&\n\t\t\t\tisArray(check.value) &&\n\t\t\t\tcheck.value.length === 0\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutput.push(checkToPremise(check, evaluator.evaluate(check, working), labels))\n\t\t}\n\t}\n\treturn output\n}\n\n/**\n * Locates an authored {@link Rule} by id.\n *\n * @param definition - The logical definition to search\n * @param id - The rule id\n * @returns The matching rule, or `undefined`\n *\n * @example\n * ```ts\n * import { findRule } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n *\n * findRule(gates, 'licensed')?.id // 'licensed'\n * ```\n */\nexport function findRule(definition: LogicalDefinition, id: string): Rule | undefined {\n\treturn definition.rules.find((rule) => rule.id === id)\n}\n\n/**\n * Projects one reason result into the internal qualification namespace.\n *\n * @remarks\n * A quantitative result contributes its numeric `value`. A logical result\n * contributes a record of its `conclusion` plus every applied rule's asserted\n * conclusion fields (`extractConclusions`). A mismatched pairing contributes\n * `false`. The projection is pure — it never mutates the subject or the result.\n *\n * @param definition - The pass definition that produced the result\n * @param result - The evaluated reason result\n * @returns The pass's working projection\n *\n * @example\n * ```ts\n * import { reasonResultToProjection } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const result = {\n * reasoning: 'quantitative' as const,\n * value: 500_000,\n * groups: [],\n * count: 1,\n * success: true,\n * trace: [],\n * errors: [],\n * }\n * reasonResultToProjection(cap, result) // 500000\n * ```\n */\nexport function reasonResultToProjection(\n\tdefinition: QualificationPass,\n\tresult: ReasonResult,\n): QualificationProjection {\n\tif (definition.reasoning === 'quantitative' && result.reasoning === 'quantitative') {\n\t\treturn result.value\n\t}\n\tif (definition.reasoning !== 'logical' || result.reasoning !== 'logical') return false\n\tconst projection: Record<string, unknown> = { conclusion: result.conclusion }\n\tfor (const resolved of result.rules) {\n\t\tif (!resolved.applied) continue\n\t\tconst authored = definition.rules.find((rule) => rule.id === resolved.id)\n\t\tif (authored === undefined) continue\n\t\tconst conclusions = extractConclusions(authored.conclusion)\n\t\tfor (const [key, value] of Object.entries(conclusions)) projection[key] = value\n\t}\n\treturn projection\n}\n\n/**\n * Projects a quantitative result into a {@link Derivation} audit record.\n *\n * @param id - The pass id\n * @param result - The evaluated quantitative result\n * @returns A fresh derivation\n *\n * @example\n * ```ts\n * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'\n *\n * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }\n * quantitativeResultToDerivation('cap', result)\n * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }\n * ```\n */\nexport function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation {\n\treturn {\n\t\tid,\n\t\tvalue: result.value,\n\t\tsuccess: result.success,\n\t\ttrace: [...result.trace],\n\t\terrors: [...result.errors],\n\t}\n}\n\n/**\n * Wraps a {@link QualificationContext} under {@link QUALIFICATION_KEY}.\n *\n * @param context - The accumulated projection context\n * @returns A record carrying the context under the reserved key\n *\n * @example\n * ```ts\n * import { qualificationToRecord } from '@orkestrel/qualifier'\n *\n * qualificationToRecord({ cap: 500_000 }) // { qualification: { cap: 500000 } }\n * ```\n */\nexport function qualificationToRecord(\n\tcontext: QualificationContext,\n): Readonly<Record<string, QualificationContext>> {\n\treturn { [QUALIFICATION_KEY]: context }\n}\n\n/**\n * Merges one pass projection into the context, copy-on-write.\n *\n * @param context - The current projection context\n * @param id - The pass id to project under\n * @param projection - The pass's working projection\n * @returns A fresh context with the projection added\n *\n * @example\n * ```ts\n * import { mergeQualificationContext } from '@orkestrel/qualifier'\n *\n * mergeQualificationContext({ cap: 500_000 }, 'excess', 250_000)\n * // { cap: 500000, excess: 250000 }\n * ```\n */\nexport function mergeQualificationContext(\n\tcontext: QualificationContext,\n\tid: string,\n\tprojection: QualificationProjection,\n): QualificationContext {\n\treturn { ...context, [id]: projection }\n}\n\n/**\n * Joins a ruling, its logical rule result, the pass, the pre-projection subject,\n * and an evaluator into a {@link Finding}.\n *\n * @remarks\n * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated\n * against the same subject snapshot the pass evaluated, so a conclusion the pass\n * derived can never satisfy its own reported premise. The message interpolates\n * against that snapshot.\n *\n * @param ruling - The authored ruling\n * @param pass - The pass the ruling's rule lives in\n * @param result - The pass's evaluated logical result\n * @param subject - The pre-projection subject snapshot the pass evaluated\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh finding\n *\n * @example\n * ```ts\n * import { rulingToFinding } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }\n * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true] }], count: 1, success: true, trace: [], errors: [] }\n * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())\n *\n * finding.applied // true\n * ```\n */\nexport function rulingToFinding(\n\truling: Ruling,\n\tpass: QualificationPass,\n\tresult: LogicalResult,\n\tsubject: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): Finding {\n\tconst entry = result.rules.find((resolved) => resolved.id === ruling.rule)\n\tconst applied = entry?.applied ?? false\n\tconst rule = pass.reasoning === 'logical' ? findRule(pass, ruling.rule) : undefined\n\tconst premises = rule === undefined ? [] : ruleToPremises(rule, subject, evaluator, labels)\n\treturn {\n\t\tid: ruling.id,\n\t\tpass: ruling.pass,\n\t\trule: ruling.rule,\n\t\teffect: ruling.effect,\n\t\t...(ruling.scope === undefined ? {} : { scope: ruling.scope }),\n\t\tapplied,\n\t\t...(ruling.message === undefined\n\t\t\t? {}\n\t\t\t: { message: interpolateMessage(ruling.message, subject) }),\n\t\tpremises,\n\t}\n}\n\n/**\n * Derives global eligibility from applied, unscoped findings.\n *\n * @remarks\n * Only findings that both applied and carry no `scope` shape global eligibility.\n * A failed pass contributes a synthetic `referral` (fail-closed), passed through\n * `failed`.\n *\n * @param findings - The resolved findings\n * @param failed - If `true`, adds a synthetic `referral` impact so an operational failure\n * fails closed; if `false`, derives eligibility from the findings alone. Default: `false`\n * @returns The most severe global eligibility\n *\n * @example\n * ```ts\n * import { deriveFindingEligibility } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const, applied: true, premises: [] }\n * deriveFindingEligibility([finding]) // 'ineligible'\n * ```\n */\nexport function deriveFindingEligibility(\n\tfindings: readonly Finding[],\n\tfailed = false,\n): Eligibility {\n\tconst eligibilities = findings\n\t\t.filter((finding) => finding.applied && finding.scope === undefined)\n\t\t.map((finding) => EFFECT_ELIGIBILITIES[finding.effect])\n\tif (failed) eligibilities.push('referral')\n\treturn combineEligibilities(eligibilities)\n}\n\n/**\n * Returns the most severe {@link Eligibility} in a list.\n *\n * @param eligibilities - The eligibilities to combine\n * @returns The most severe eligibility, or `'eligible'` for an empty list\n *\n * @example\n * ```ts\n * import { combineEligibilities } from '@orkestrel/qualifier'\n *\n * combineEligibilities(['eligible', 'referral']) // 'referral'\n * ```\n */\nexport function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility {\n\tfor (const eligibility of ELIGIBILITY_PRECEDENCE) {\n\t\tif (eligibilities.includes(eligibility)) return eligibility\n\t}\n\treturn 'eligible'\n}\n\n/**\n * Derives one eligibility per finding scope.\n *\n * @remarks\n * Only applied, scoped findings contribute. Each named scope combines its own\n * findings' impacts by severity. An unmentioned scope is absent (eligible by\n * default at the program layer).\n *\n * @param findings - The resolved findings\n * @returns A fresh record of scope to its combined eligibility\n *\n * @example\n * ```ts\n * import { deriveScopeEligibilities } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'coastal', pass: 'wind', rule: 'coastal', effect: 'restriction' as const, scope: 'wind', applied: true, premises: [] }\n * deriveScopeEligibilities([finding]) // { wind: 'ineligible' }\n * ```\n */\nexport function deriveScopeEligibilities(\n\tfindings: readonly Finding[],\n): Readonly<Record<string, Eligibility>> {\n\tconst grouped: Record<string, Eligibility[]> = {}\n\tfor (const finding of findings) {\n\t\tif (!finding.applied || finding.scope === undefined) continue\n\t\tconst current = grouped[finding.scope] ?? []\n\t\tgrouped[finding.scope] = [...current, EFFECT_ELIGIBILITIES[finding.effect]]\n\t}\n\tconst scopes: Record<string, Eligibility> = {}\n\tfor (const [scope, eligibilities] of Object.entries(grouped)) {\n\t\tscopes[scope] = combineEligibilities(eligibilities)\n\t}\n\treturn scopes\n}\n\n/**\n * Describes each ruling whose pass or rule does not exist or whose pass is not\n * logical, and each pass id shadowing the reserved {@link QUALIFICATION_KEY}.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of reference-error messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, createRuling, describeMissingReferences } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates], {\n * rulings: [createRuling('license', 'gates', 'absent', 'restriction')],\n * })\n *\n * describeMissingReferences(definition)\n * // [\"Ruling 'license' references missing rule 'absent' in pass 'gates'\"]\n * ```\n */\nexport function describeMissingReferences(definition: QualificationDefinition): readonly string[] {\n\tconst errors: string[] = []\n\tconst passes = new Map(definition.passes.map((pass) => [pass.id, pass]))\n\tfor (const pass of definition.passes) {\n\t\tif (pass.id === QUALIFICATION_KEY) {\n\t\t\terrors.push(`Pass id must not equal reserved key '${QUALIFICATION_KEY}'`)\n\t\t}\n\t}\n\tfor (const ruling of definition.rulings ?? []) {\n\t\tconst pass = passes.get(ruling.pass)\n\t\tif (pass === undefined) {\n\t\t\terrors.push(`Ruling '${ruling.id}' references missing pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (pass.reasoning !== 'logical') {\n\t\t\terrors.push(`Ruling '${ruling.id}' references non-logical pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (findRule(pass, ruling.rule) === undefined) {\n\t\t\terrors.push(\n\t\t\t\t`Ruling '${ruling.id}' references missing rule '${ruling.rule}' in pass '${ruling.pass}'`,\n\t\t\t)\n\t\t}\n\t}\n\treturn errors\n}\n\n/**\n * Determines whether a subject already owns the reserved {@link QUALIFICATION_KEY}.\n *\n * @param subject - The subject to check\n * @returns True if the subject owns the reserved key; false otherwise\n *\n * @example\n * ```ts\n * import { hasReservedKey } from '@orkestrel/qualifier'\n *\n * hasReservedKey({ id: 's1', qualification: {} }) // true\n * ```\n */\nexport function hasReservedKey(subject: Subject): boolean {\n\treturn Object.hasOwn(subject, QUALIFICATION_KEY)\n}\n\n/**\n * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.\n *\n * @param value - The candidate subject to validate\n * @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or\n * when it already carries the reserved `qualification` key\n *\n * @example\n * ```ts\n * import { assertSubject } from '@orkestrel/qualifier'\n *\n * assertSubject({ id: 's1' }) // narrows to Subject\n * ```\n */\nexport function assertSubject(value: unknown): asserts value is Subject {\n\tif (!isRecord(value)) {\n\t\tthrow new QualifierError('MISMATCH', 'Qualification subject must be a record')\n\t}\n\tif (hasReservedKey(value)) {\n\t\tthrow new QualifierError(\n\t\t\t'MISMATCH',\n\t\t\t`Qualification subject must not contain reserved key '${QUALIFICATION_KEY}'`,\n\t\t)\n\t}\n}\n\n/**\n * Maps an engine throw caught while running one pass to a typed {@link QualifierError}.\n *\n * @remarks\n * A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the\n * pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;\n * every other `ReasonError` code (for example `MISSING`) and every non-`ReasonError` throw\n * (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw\n * is preserved as `context.cause`.\n *\n * @param error - The value caught from the engine's `reason` call\n * @param pass - The id of the pass that was running\n * @returns A fresh, typed {@link QualifierError}\n *\n * @example\n * ```ts\n * import { mapEngineError } from '@orkestrel/qualifier'\n * import { ReasonError } from '@orkestrel/reason'\n *\n * mapEngineError(new ReasonError('MISSING', 'No reasoner registered'), 'cap').code // 'ENGINE'\n * ```\n */\nexport function mapEngineError(error: unknown, pass: string): QualifierError {\n\tif (isReasonError(error)) {\n\t\tif (error.code === 'INVALID') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DEFINITION',\n\t\t\t\t`Pass '${pass}' failed engine validation: ${error.message}`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\tif (error.code === 'DESTROYED') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DESTROYED',\n\t\t\t\t`Qualifier engine was destroyed during pass '${pass}'`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${error.message}`, {\n\t\t\tpass,\n\t\t\tcause: error,\n\t\t})\n\t}\n\tconst message = isError(error) ? error.message : String(error)\n\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${message}`, {\n\t\tpass,\n\t\tcause: error,\n\t})\n}\n\n/**\n * Describes each logical pass carrying no rulings.\n *\n * @remarks\n * A logical pass with no matching ruling can never contribute a finding, so its\n * rules only ever influence later passes' reads — this is very likely an authoring\n * oversight.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeEmptyLogicalPasses } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates])\n *\n * describeEmptyLogicalPasses(definition) // [\"Logical pass 'gates' has no rulings\"]\n * ```\n */\nexport function describeEmptyLogicalPasses(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tfor (const pass of definition.passes) {\n\t\tif (pass.reasoning !== 'logical') continue\n\t\tconst hasRuling = (definition.rulings ?? []).some((ruling) => ruling.pass === pass.id)\n\t\tif (!hasRuling) warnings.push(`Logical pass '${pass.id}' has no rulings`)\n\t}\n\treturn warnings\n}\n\n/**\n * Describes each quantitative pass never read by a later pass.\n *\n * @remarks\n * A quantitative pass's projected value lives under `qualification.<id>` — if no\n * later logical premise or quantitative factor field reads that key, the\n * derivation is dead weight. Reads are collected per later pass: a logical pass's\n * premise atoms (conclusion atoms are writes, not reads), and a quantitative\n * pass's field/lookup/range factor sources plus factor checks.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeUnreadDerivations } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [cap])\n *\n * describeUnreadDerivations(definition) // [\"Quantitative pass 'cap' is never read by a later pass\"]\n * ```\n */\nexport function describeUnreadDerivations(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tdefinition.passes.forEach((pass, index) => {\n\t\tif (pass.reasoning !== 'quantitative') return\n\t\tconst key = formatField(['qualification', pass.id])\n\t\tconst reads = new Set<string>()\n\t\tfor (const later of definition.passes.slice(index + 1)) {\n\t\t\tif (later.reasoning === 'logical') {\n\t\t\t\tfor (const rule of later.rules) {\n\t\t\t\t\tfor (const premise of rule.premises) {\n\t\t\t\t\t\tfor (const atom of extractAtoms(premise)) reads.add(formatField(atom.check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (later.reasoning === 'quantitative') {\n\t\t\t\tfor (const group of later.groups) {\n\t\t\t\t\tfor (const factor of group.factors) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tfactor.source.origin === 'field' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'lookup' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'range'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treads.add(formatField(factor.source.field))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const check of factor.checks ?? []) reads.add(formatField(check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!reads.has(key)) {\n\t\t\twarnings.push(`Quantitative pass '${pass.id}' is never read by a later pass`)\n\t\t}\n\t})\n\treturn warnings\n}\n","import type {\n\tDerivation,\n\tFinding,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tQualifierEventMap,\n\tQualifierInterface,\n\tQualifierOptions,\n} from './types.js'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tEvaluatorInterface,\n\tReasonInterface,\n\tReasonResult,\n\tReasonValidationResult,\n\tSubject,\n} from '@orkestrel/reason'\nimport { DEFAULT_QUALIFIER_VALIDATE } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport {\n\tassertSubject,\n\tdescribeEmptyLogicalPasses,\n\tdescribeMissingReferences,\n\tdescribeUnreadDerivations,\n\tderiveFindingEligibility,\n\tderiveScopeEligibilities,\n\tmapEngineError,\n\tmergeQualificationContext,\n\tqualificationToRecord,\n\tquantitativeResultToDerivation,\n\treasonResultToProjection,\n\trulingToFinding,\n} from './helpers.js'\nimport { isQualificationPass, isRuling } from './validators.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcreateEvaluator,\n\tcreateLogicalReasoner,\n\tcreateQuantitativeReasoner,\n\tcreateReason,\n\tfindDuplicates,\n} from '@orkestrel/reason'\n\n/**\n * Runs ordered passes over one reason engine and returns eligibility.\n *\n * @remarks\n * The engine is owned when self-created (destroyed on `destroy()`) and borrowed\n * when injected (never destroyed). Semantic validation runs before the first pass\n * whenever the `validate` option is on. `qualify` builds its working subject through\n * copy-on-write overlays only — the caller's `subject` is never mutated, and the\n * qualifier's internal projection is discarded after each call.\n */\nexport class Qualifier implements QualifierInterface {\n\treadonly #emitter: Emitter<QualifierEventMap>\n\treadonly #engine: ReasonInterface\n\treadonly #evaluator: EvaluatorInterface\n\treadonly #owned: boolean\n\treadonly #validate: boolean\n\treadonly #labels: Readonly<Record<string, string>> | undefined\n\t#destroyed = false\n\n\tconstructor(options?: QualifierOptions) {\n\t\tthis.#emitter = new Emitter({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\tthis.#evaluator = createEvaluator()\n\t\tthis.#owned = options?.engine === undefined\n\t\tthis.#engine =\n\t\t\toptions?.engine ??\n\t\t\tcreateReason({\n\t\t\t\treasoners: [\n\t\t\t\t\tcreateQuantitativeReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t\tcreateLogicalReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t],\n\t\t\t\tbail: false,\n\t\t\t})\n\t\tthis.#validate = options?.validate ?? DEFAULT_QUALIFIER_VALIDATE\n\t\tthis.#labels = options?.labels\n\t}\n\n\t/**\n\t * Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and\n\t * `destroy`.\n\t *\n\t * @returns The emitter this qualifier owns\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.emitter.on('qualify', (result) => console.log(result.eligibility))\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tget emitter(): EmitterInterface<QualifierEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/**\n\t * Qualifies one subject against one authored definition.\n\t *\n\t * @remarks\n\t * Semantic validation runs first when the `validate` option is on, so an invalid\n\t * definition throws before any pass runs. Passes run in authored order over a\n\t * copy-on-write working subject, and the caller's `subject` is never mutated. An\n\t * unscoped restriction stops the remaining passes because no later finding can be\n\t * more severe.\n\t *\n\t * @param subject - The record to qualify\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh, frozen qualification result\n\t * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the\n\t * definition fails semantic validation, or when the engine rejects a pass as an invalid\n\t * definition; `'MISMATCH'` when the subject is not a record or already carries the\n\t * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine\n\t * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, createRuling, Qualifier } from '@orkestrel/qualifier'\n\t * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n\t *\n\t * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n\t * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n\t * ])\n\t * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {\n\t * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],\n\t * })\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tqualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tthis.#alive()\n\t\tif (this.#validate) {\n\t\t\tconst validation = this.validate(definition)\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new QualifierError(\n\t\t\t\t\t'DEFINITION',\n\t\t\t\t\t`Qualification definition is invalid: ${validation.errors.join(', ')}`,\n\t\t\t\t\t{ definition: definition.id },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn this.#qualify(subject, definition)\n\t}\n\n\t/**\n\t * Validates one authored definition semantically, without running it.\n\t *\n\t * @remarks\n\t * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:\n\t * non-empty id and name, valid and uniquely identified passes and rulings, every\n\t * ruling reference resolving to a logical pass and one of its rules, and no pass id\n\t * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no\n\t * ruling, and a derivation no later pass reads are warnings rather than errors.\n\t *\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`\n\t * @throws {@link QualifierError} `'DESTROYED'` after `destroy`\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))\n\t * // { valid: true, errors: [], warnings: ['Definition has no passes'] }\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tvalidate(definition: QualificationDefinition): ReasonValidationResult {\n\t\tthis.#alive()\n\t\tconst errors: string[] = []\n\t\tconst warnings: string[] = []\n\t\tif (definition.id.length === 0) errors.push('Definition id must not be empty')\n\t\tif (definition.name.length === 0) errors.push('Definition name must not be empty')\n\t\tdefinition.passes.forEach((pass, index) => {\n\t\t\tif (!isQualificationPass(pass)) {\n\t\t\t\terrors.push(`Pass at index ${index} is not a valid qualification pass`)\n\t\t\t}\n\t\t})\n\t\t;(definition.rulings ?? []).forEach((ruling, index) => {\n\t\t\tif (!isRuling(ruling)) errors.push(`Ruling at index ${index} is not a valid ruling`)\n\t\t})\n\t\tfor (const id of findDuplicates(definition.passes)) errors.push(`Duplicate pass id '${id}'`)\n\t\tfor (const id of findDuplicates(definition.rulings ?? [])) {\n\t\t\terrors.push(`Duplicate ruling id '${id}'`)\n\t\t}\n\t\tfor (const reference of describeMissingReferences(definition)) errors.push(reference)\n\t\tif (definition.passes.length === 0) warnings.push('Definition has no passes')\n\t\tfor (const warning of describeEmptyLogicalPasses(definition)) warnings.push(warning)\n\t\tfor (const warning of describeUnreadDerivations(definition)) warnings.push(warning)\n\t\treturn { valid: errors.length === 0, errors, warnings }\n\t}\n\n\t/**\n\t * Destroys this qualifier, idempotently.\n\t *\n\t * @remarks\n\t * An engine this qualifier created is destroyed; an injected engine stays\n\t * caller-owned and is left alone. The `destroy` event fires before the emitter\n\t * itself is destroyed, and a later call returns without re-emitting.\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.destroy()\n\t * qualifier.destroy() // idempotent\n\t * ```\n\t */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tif (this.#owned) this.#engine.destroy()\n\t\tthis.#destroyed = true\n\t\tthis.#emitter.emit('destroy')\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#qualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tassertSubject(subject)\n\t\tconst steps: Array<{\n\t\t\treadonly context: QualificationContext\n\t\t\treadonly subject: Subject\n\t\t\treadonly success: boolean\n\t\t}> = [{ context: {}, subject: { ...subject }, success: true }]\n\t\tconst findings: Finding[] = []\n\t\tconst derivations: Derivation[] = []\n\t\tconst trace: string[] = []\n\t\tconst errors: string[] = []\n\n\t\tfor (const pass of definition.passes) {\n\t\t\tthis.#alive()\n\t\t\tconst previous = steps.at(-1)\n\t\t\tif (previous === undefined) throw new Error('Qualification has no initial step')\n\t\t\tconst evaluated = previous.subject\n\t\t\tconst result = this.#reason(evaluated, pass)\n\n\t\t\ttrace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`))\n\t\t\terrors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`))\n\n\t\t\tif (result.reasoning === 'quantitative') {\n\t\t\t\tconst derivation = quantitativeResultToDerivation(pass.id, result)\n\t\t\t\tderivations.push(derivation)\n\t\t\t\tthis.#emitter.emit('derive', derivation)\n\t\t\t}\n\n\t\t\tif (result.reasoning === 'logical') {\n\t\t\t\tconst rulings = (definition.rulings ?? []).filter((ruling) => ruling.pass === pass.id)\n\t\t\t\tfor (const ruling of rulings) {\n\t\t\t\t\tconst finding = rulingToFinding(\n\t\t\t\t\t\truling,\n\t\t\t\t\t\tpass,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tevaluated,\n\t\t\t\t\t\tthis.#evaluator,\n\t\t\t\t\t\tthis.#labels,\n\t\t\t\t\t)\n\t\t\t\t\tfindings.push(finding)\n\t\t\t\t\tthis.#emitter.emit('finding', finding)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst projection = reasonResultToProjection(pass, result)\n\t\t\tconst context = mergeQualificationContext(previous.context, pass.id, projection)\n\t\t\tsteps.push({\n\t\t\t\tcontext,\n\t\t\t\tsubject: { ...subject, ...qualificationToRecord(context) },\n\t\t\t\tsuccess: result.success,\n\t\t\t})\n\n\t\t\tif (!result.success) break\n\t\t\tif (deriveFindingEligibility(findings) === 'ineligible') break\n\t\t}\n\n\t\tconst success = steps.every((step) => step.success) && errors.length === 0\n\t\tconst scopes = deriveScopeEligibilities(findings)\n\t\tconst qualification: QualificationResult = Object.freeze({\n\t\t\tid: definition.id,\n\t\t\tname: definition.name,\n\t\t\teligibility: deriveFindingEligibility(findings, !success),\n\t\t\tscopes: Object.freeze(scopes),\n\t\t\tfindings: Object.freeze(findings),\n\t\t\tderivations: Object.freeze(derivations),\n\t\t\tsuccess,\n\t\t\ttrace: Object.freeze(trace),\n\t\t\terrors: Object.freeze(errors),\n\t\t})\n\t\tthis.#emitter.emit('qualify', qualification)\n\t\treturn qualification\n\t}\n\n\t#reason(subject: Subject, pass: QualificationPass): ReasonResult {\n\t\ttry {\n\t\t\treturn this.#engine.reason(subject, pass)\n\t\t} catch (error) {\n\t\t\tthrow mapEngineError(error, pass.id)\n\t\t}\n\t}\n\n\t#alive(): void {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new QualifierError('DESTROYED', 'Qualifier has been destroyed')\n\t\t}\n\t}\n}\n","import type {\n\tQualificationDefinition,\n\tQualificationEffect,\n\tQualificationInput,\n\tQualificationPass,\n\tQualifierInterface,\n\tQualifierOptions,\n\tRuling,\n\tRulingInput,\n} from './types.js'\nimport { Qualifier } from './Qualifier.js'\n\n/**\n * Creates one {@link QualifierInterface} over a reason engine.\n *\n * @remarks\n * A standalone qualifier creates and owns one shared quantitative-plus-logical\n * reason engine, destroying it on `destroy()`. An injected `options.engine`\n * remains caller-owned and is never destroyed. `validate` runs semantic\n * validation before every qualification. Default: `true`. `labels` overrides the\n * display name of a premise field, keyed by the dot-joined field path\n * `formatField` produces, and takes precedence over a premise's own `label`.\n *\n * @param options - Optional injected engine, validation, labels, and emitter hooks\n * @returns A {@link QualifierInterface}\n *\n * @example Create a qualifier\n * ```ts\n * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n * \tcreateRule(\n * \t\t'licensed',\n * \t\t[createAtom('licensed', 'equals', false)],\n * \t\tcreateAtom('blocked', 'equals', true),\n * \t),\n * ])\n *\n * const bare = createRuling('license', 'gates', 'licensed', 'restriction')\n * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {\n * \tmessage: 'A license is required',\n * })\n *\n * 'message' in bare // false — an absent optional key is omitted, never written as undefined\n * messaged.message // 'A license is required'\n *\n * const passes = [gates]\n * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {\n * \trulings: [messaged],\n * })\n *\n * 'description' in definition // false\n * definition.passes === passes // false — the factory copies what it is handed\n *\n * const qualifier = createQualifier()\n * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)\n * qualifier.destroy()\n * ```\n */\nexport function createQualifier(options?: QualifierOptions): QualifierInterface {\n\treturn new Qualifier(options)\n}\n\n/**\n * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.\n *\n * @remarks\n * `passes` and `rulings` arrays are copied, and record `metadata` is\n * shallow-copied so nested values are not deep-cloned.\n *\n * @param id - The definition id\n * @param name - The display name\n * @param passes - The ordered qualification passes\n * @param input - Optional description, rulings, and metadata\n * @returns A fresh qualification definition\n *\n * @example\n * ```ts\n * import { createQualificationDefinition } from '@orkestrel/qualifier'\n *\n * createQualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })\n * ```\n */\nexport function createQualificationDefinition(\n\tid: string,\n\tname: string,\n\tpasses: readonly QualificationPass[],\n\tinput?: QualificationInput,\n): QualificationDefinition {\n\treturn {\n\t\tid,\n\t\tname,\n\t\tpasses: [...passes],\n\t\t...(input?.description === undefined ? {} : { description: input.description }),\n\t\t...(input?.rulings === undefined ? {} : { rulings: [...input.rulings] }),\n\t\t...(input?.metadata === undefined ? {} : { metadata: { ...input.metadata } }),\n\t}\n}\n\n/**\n * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.\n *\n * @param id - The ruling id\n * @param pass - The logical pass id the rule lives in\n * @param rule - The rule id whose firing this ruling reacts to\n * @param effect - The eligibility impact when the rule fires\n * @param input - Optional selection scope and message template\n * @returns A fresh ruling\n *\n * @example\n * ```ts\n * import { createRuling } from '@orkestrel/qualifier'\n *\n * createRuling('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })\n * ```\n */\nexport function createRuling(\n\tid: string,\n\tpass: string,\n\trule: string,\n\teffect: QualificationEffect,\n\tinput?: RulingInput,\n): Ruling {\n\treturn {\n\t\tid,\n\t\tpass,\n\t\trule,\n\t\teffect,\n\t\t...(input?.scope === undefined ? {} : { scope: input.scope }),\n\t\t...(input?.message === undefined ? {} : { message: input.message }),\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -475,7 +475,7 @@ import type { Subject } from '@orkestrel/reason';
|
|
|
475
475
|
* @param value - The value to test
|
|
476
476
|
* @returns True if `value` is one of the effect literals; false otherwise
|
|
477
477
|
*/
|
|
478
|
-
export declare const isQualificationEffect: Guard<"
|
|
478
|
+
export declare const isQualificationEffect: Guard<"condition" | "referral" | "restriction">;
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
481
|
* Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
|
|
@@ -509,7 +509,7 @@ import type { Subject } from '@orkestrel/reason';
|
|
|
509
509
|
* Narrows a caught value to a {@link QualifierError}.
|
|
510
510
|
*
|
|
511
511
|
* @param value - The caught value to test
|
|
512
|
-
* @returns True if
|
|
512
|
+
* @returns True if the error carries its own identity brand and declared members; false otherwise
|
|
513
513
|
*/
|
|
514
514
|
export declare function isQualifierError(value: unknown): value is QualifierError;
|
|
515
515
|
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -475,7 +475,7 @@ import type { Subject } from '@orkestrel/reason';
|
|
|
475
475
|
* @param value - The value to test
|
|
476
476
|
* @returns True if `value` is one of the effect literals; false otherwise
|
|
477
477
|
*/
|
|
478
|
-
export declare const isQualificationEffect: Guard<"
|
|
478
|
+
export declare const isQualificationEffect: Guard<"condition" | "referral" | "restriction">;
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
481
|
* Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
|
|
@@ -509,7 +509,7 @@ import type { Subject } from '@orkestrel/reason';
|
|
|
509
509
|
* Narrows a caught value to a {@link QualifierError}.
|
|
510
510
|
*
|
|
511
511
|
* @param value - The caught value to test
|
|
512
|
-
* @returns True if
|
|
512
|
+
* @returns True if the error carries its own identity brand and declared members; false otherwise
|
|
513
513
|
*/
|
|
514
514
|
export declare function isQualifierError(value: unknown): value is QualifierError;
|
|
515
515
|
|
package/dist/src/core/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { arrayOf, isArray, isBoolean, isError, isFiniteNumber,
|
|
1
|
+
import { INTRINSICS, arrayOf, holds, isArray, isBoolean, isError, isFiniteNumber, isFunction, isNumber, isObject, isRecord, isString, literalOf, objectOf, recordOf, resolveField, whereOf } from "@orkestrel/contract";
|
|
2
2
|
import { createEvaluator, createLogicalReasoner, createQuantitativeReasoner, createReason, extractAtoms, extractConclusions, findDuplicates, formatField, isBounds, isComparison, isFieldPath, isLogicalDefinition, isQuantitativeDefinition, isReasonError } from "@orkestrel/reason";
|
|
3
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
4
|
-
|
|
4
|
+
// src/core/constants.ts
|
|
5
5
|
/**
|
|
6
6
|
* Holds the default definition validation policy for `createQualifier` and
|
|
7
7
|
* `Qualifier.qualify`, `true`.
|
|
@@ -27,8 +27,7 @@ var EFFECT_ELIGIBILITIES = Object.freeze({
|
|
|
27
27
|
referral: "referral",
|
|
28
28
|
condition: "eligible"
|
|
29
29
|
});
|
|
30
|
-
|
|
31
|
-
//#region src/core/errors.ts
|
|
30
|
+
// src/core/errors.ts
|
|
32
31
|
/**
|
|
33
32
|
* Represents a coded programmer error thrown by the qualifier layer, carrying a
|
|
34
33
|
* `QualifierErrorCode` and an optional context record.
|
|
@@ -50,6 +49,7 @@ var QualifierError = class extends Error {
|
|
|
50
49
|
context;
|
|
51
50
|
constructor(code, message, context) {
|
|
52
51
|
super(message);
|
|
52
|
+
INTRINSICS.define(this, Symbol.for("@orkestrel/qualifier.QualifierError"), { value: this });
|
|
53
53
|
this.name = "QualifierError";
|
|
54
54
|
this.code = code;
|
|
55
55
|
this.context = context;
|
|
@@ -59,13 +59,23 @@ var QualifierError = class extends Error {
|
|
|
59
59
|
* Narrows a caught value to a {@link QualifierError}.
|
|
60
60
|
*
|
|
61
61
|
* @param value - The caught value to test
|
|
62
|
-
* @returns True if
|
|
62
|
+
* @returns True if the error carries its own identity brand and declared members; false otherwise
|
|
63
63
|
*/
|
|
64
64
|
function isQualifierError(value) {
|
|
65
|
-
return
|
|
65
|
+
return holds(() => {
|
|
66
|
+
if (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
67
|
+
if (INTRINSICS.describe(value, Symbol.for("@orkestrel/qualifier.QualifierError"))?.value !== value) return false;
|
|
68
|
+
const code = INTRINSICS.reflect.read(value, "code");
|
|
69
|
+
if (code !== "DEFINITION" && code !== "MISMATCH" && code !== "DESTROYED" && code !== "ENGINE") return false;
|
|
70
|
+
const context = INTRINSICS.reflect.read(value, "context");
|
|
71
|
+
if (context === void 0) return true;
|
|
72
|
+
if (!isObject(context) && !isFunction(context)) return false;
|
|
73
|
+
const pass = INTRINSICS.reflect.read(context, "pass");
|
|
74
|
+
const definition = INTRINSICS.reflect.read(context, "definition");
|
|
75
|
+
return (pass === void 0 || typeof pass === "string") && (definition === void 0 || typeof definition === "string");
|
|
76
|
+
});
|
|
66
77
|
}
|
|
67
|
-
|
|
68
|
-
//#region src/core/validators.ts
|
|
78
|
+
// src/core/validators.ts
|
|
69
79
|
/**
|
|
70
80
|
* Determines whether a value is an {@link Eligibility} literal.
|
|
71
81
|
*
|
|
@@ -261,8 +271,7 @@ function isQualificationDefinition(value) {
|
|
|
261
271
|
"metadata"
|
|
262
272
|
])(value);
|
|
263
273
|
}
|
|
264
|
-
|
|
265
|
-
//#region src/core/helpers.ts
|
|
274
|
+
// src/core/helpers.ts
|
|
266
275
|
/**
|
|
267
276
|
* Interpolates `{{dotted.path}}` tokens in a message template against a subject.
|
|
268
277
|
*
|
|
@@ -898,8 +907,7 @@ function describeUnreadDerivations(definition) {
|
|
|
898
907
|
});
|
|
899
908
|
return warnings;
|
|
900
909
|
}
|
|
901
|
-
|
|
902
|
-
//#region src/core/Qualifier.ts
|
|
910
|
+
// src/core/Qualifier.ts
|
|
903
911
|
/**
|
|
904
912
|
* Runs ordered passes over one reason engine and returns eligibility.
|
|
905
913
|
*
|
|
@@ -1068,22 +1076,21 @@ var Qualifier = class {
|
|
|
1068
1076
|
}
|
|
1069
1077
|
#qualify(subject, definition) {
|
|
1070
1078
|
assertSubject(subject);
|
|
1071
|
-
|
|
1072
|
-
|
|
1079
|
+
const steps = [{
|
|
1080
|
+
context: {},
|
|
1081
|
+
subject: { ...subject },
|
|
1082
|
+
success: true
|
|
1083
|
+
}];
|
|
1073
1084
|
const findings = [];
|
|
1074
1085
|
const derivations = [];
|
|
1075
1086
|
const trace = [];
|
|
1076
1087
|
const errors = [];
|
|
1077
|
-
let failed = false;
|
|
1078
1088
|
for (const pass of definition.passes) {
|
|
1079
1089
|
this.#alive();
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
} catch (error) {
|
|
1085
|
-
throw mapEngineError(error, pass.id);
|
|
1086
|
-
}
|
|
1090
|
+
const previous = steps.at(-1);
|
|
1091
|
+
if (previous === void 0) throw new Error("Qualification has no initial step");
|
|
1092
|
+
const evaluated = previous.subject;
|
|
1093
|
+
const result = this.#reason(evaluated, pass);
|
|
1087
1094
|
trace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`));
|
|
1088
1095
|
errors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`));
|
|
1089
1096
|
if (result.reasoning === "quantitative") {
|
|
@@ -1100,18 +1107,19 @@ var Qualifier = class {
|
|
|
1100
1107
|
}
|
|
1101
1108
|
}
|
|
1102
1109
|
const projection = reasonResultToProjection(pass, result);
|
|
1103
|
-
context = mergeQualificationContext(context, pass.id, projection);
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1110
|
+
const context = mergeQualificationContext(previous.context, pass.id, projection);
|
|
1111
|
+
steps.push({
|
|
1112
|
+
context,
|
|
1113
|
+
subject: {
|
|
1114
|
+
...subject,
|
|
1115
|
+
...qualificationToRecord(context)
|
|
1116
|
+
},
|
|
1117
|
+
success: result.success
|
|
1118
|
+
});
|
|
1119
|
+
if (!result.success) break;
|
|
1112
1120
|
if (deriveFindingEligibility(findings) === "ineligible") break;
|
|
1113
1121
|
}
|
|
1114
|
-
const success =
|
|
1122
|
+
const success = steps.every((step) => step.success) && errors.length === 0;
|
|
1115
1123
|
const scopes = deriveScopeEligibilities(findings);
|
|
1116
1124
|
const qualification = Object.freeze({
|
|
1117
1125
|
id: definition.id,
|
|
@@ -1127,12 +1135,18 @@ var Qualifier = class {
|
|
|
1127
1135
|
this.#emitter.emit("qualify", qualification);
|
|
1128
1136
|
return qualification;
|
|
1129
1137
|
}
|
|
1138
|
+
#reason(subject, pass) {
|
|
1139
|
+
try {
|
|
1140
|
+
return this.#engine.reason(subject, pass);
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
throw mapEngineError(error, pass.id);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1130
1145
|
#alive() {
|
|
1131
1146
|
if (this.#destroyed) throw new QualifierError("DESTROYED", "Qualifier has been destroyed");
|
|
1132
1147
|
}
|
|
1133
1148
|
};
|
|
1134
|
-
|
|
1135
|
-
//#region src/core/factories.ts
|
|
1149
|
+
// src/core/factories.ts
|
|
1136
1150
|
/**
|
|
1137
1151
|
* Creates one {@link QualifierInterface} over a reason engine.
|
|
1138
1152
|
*
|
|
@@ -1241,7 +1255,6 @@ function createRuling(id, pass, rule, effect, input) {
|
|
|
1241
1255
|
...input?.message === void 0 ? {} : { message: input.message }
|
|
1242
1256
|
};
|
|
1243
1257
|
}
|
|
1244
|
-
//#endregion
|
|
1245
1258
|
export { DEFAULT_QUALIFIER_VALIDATE, EFFECT_ELIGIBILITIES, ELIGIBILITY_PRECEDENCE, QUALIFICATION_KEY, Qualifier, QualifierError, assertSubject, checkToPremise, combineEligibilities, createQualificationDefinition, createQualifier, createRuling, deriveFindingEligibility, deriveScopeEligibilities, describeEmptyLogicalPasses, describeMissingReferences, describeUnreadDerivations, findRule, hasReservedKey, interpolateMessage, isDerivation, isEligibility, isEligibilityRecord, isFinding, isPremise, isQualificationDefinition, isQualificationEffect, isQualificationPass, isQualificationResult, isQualifierError, isRuling, mapEngineError, mergeQualificationContext, qualificationToRecord, quantitativeResultToDerivation, reasonResultToProjection, renderComparison, renderPremise, renderValue, ruleToPremises, rulingToFinding };
|
|
1246
1259
|
|
|
1247
1260
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Qualifier.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { Eligibility, QualificationEffect } from './types.js'\n\n/**\n * Holds the default definition validation policy for `createQualifier` and\n * `Qualifier.qualify`, `true`.\n */\nexport const DEFAULT_QUALIFIER_VALIDATE = true\n\n/**\n * Names `'qualification'`, the reserved internal projection namespace a pass's working\n * projection is written under.\n */\nexport const QUALIFICATION_KEY = 'qualification'\n\n/** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */\nexport const ELIGIBILITY_PRECEDENCE: readonly Eligibility[] = Object.freeze([\n\t'ineligible',\n\t'referral',\n\t'eligible',\n])\n\n/**\n * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to\n * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.\n */\nexport const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>> =\n\tObject.freeze({\n\t\trestriction: 'ineligible',\n\t\treferral: 'referral',\n\t\tcondition: 'eligible',\n\t})\n","import type { QualifierErrorCode, QualifierErrorContext } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Represents a coded programmer error thrown by the qualifier layer, carrying a\n * `QualifierErrorCode` and an optional context record.\n *\n * @remarks\n * `DEFINITION` — a qualification definition failed semantic validation; when\n * `qualify`'s own validation raises it, `context.definition` names the\n * definition, and when an engine throw maps to it, `context.pass` names the pass\n * and `context.cause` preserves the original throw. `MISMATCH` — a subject is\n * not a record or already carries the reserved `qualification` key.\n * `DESTROYED` — use of a destroyed qualifier; an engine-mapped one carries\n * `context.pass` and `context.cause`. `ENGINE` — the underlying reason engine\n * threw while running a pass; `context.pass` names the pass and `context.cause`\n * preserves the original throw. `context` is absent for a throw that carries no\n * payload.\n */\nexport class QualifierError extends Error {\n\treadonly code: QualifierErrorCode\n\treadonly context: QualifierErrorContext | undefined\n\n\tconstructor(code: QualifierErrorCode, message: string, context?: QualifierErrorContext) {\n\t\tsuper(message)\n\t\tthis.name = 'QualifierError'\n\t\tthis.code = code\n\t\tthis.context = context\n\t}\n}\n\n/**\n * Narrows a caught value to a {@link QualifierError}.\n *\n * @param value - The caught value to test\n * @returns True if `value` is a {@link QualifierError} instance; false otherwise\n */\nexport function isQualifierError(value: unknown): value is QualifierError {\n\treturn isInstance(value, QualifierError)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tRuling,\n} from './types.js'\nimport {\n\tarrayOf,\n\tisBoolean,\n\tisNumber,\n\tisRecord,\n\tisString,\n\tliteralOf,\n\tobjectOf,\n\trecordOf,\n\twhereOf,\n} from '@orkestrel/contract'\nimport {\n\tisComparison,\n\tisFieldPath,\n\tisLogicalDefinition,\n\tisQuantitativeDefinition,\n} from '@orkestrel/reason'\n\n/**\n * Determines whether a value is an {@link Eligibility} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the eligibility literals; false otherwise\n */\nexport const isEligibility = literalOf('eligible', 'ineligible', 'referral')\n\n/**\n * Determines whether a value is a {@link QualificationEffect} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the effect literals; false otherwise\n */\nexport const isQualificationEffect = literalOf('restriction', 'referral', 'condition')\n\n/**\n * Determines whether a value is an open string-keyed record of {@link Eligibility} values.\n *\n * @remarks\n * Every own string-named value is checked through {@link isEligibility}, including\n * non-enumerable values. Inherited and symbol-named members are left unchecked because they\n * are outside the own string-keyed record this guard certifies.\n *\n * @param value - The value to test\n * @returns True if `value` is a non-array object whose own string-named values are\n * eligibilities; false otherwise\n */\nexport function isEligibilityRecord(\n\tvalue: unknown,\n): value is Readonly<Record<string, Eligibility>> {\n\treturn whereOf(objectOf({}), (record): record is Readonly<Record<string, Eligibility>> =>\n\t\tObject.getOwnPropertyNames(record).every((key) => isEligibility(Reflect.get(record, key))),\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Premise}.\n *\n * @remarks\n * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked\n * when they are defined. `expected` is unchecked and may be absent or contain any value\n * because its published type is `unknown`. `actual` is unchecked and may be absent or contain\n * any value for the same reason. Unknown members are admitted, because an open result admits\n * what the published contract does not name.\n *\n * @param value - The value to test\n * @returns True if every checked premise member follows its published type; false otherwise\n */\nexport function isPremise(value: unknown): value is Premise {\n\treturn objectOf(\n\t\t{\n\t\t\tfield: isFieldPath,\n\t\t\tlabel: isString,\n\t\t\tdescription: isString,\n\t\t\tcomparison: isComparison,\n\t\t\tmet: isBoolean,\n\t\t},\n\t\t['field', 'label', 'description', 'comparison', 'met'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Finding}.\n *\n * @remarks\n * Every published member is checked, including each nested {@link Premise}. Optional `scope`\n * and `message` members may be absent or `undefined`; a present defined value must be a\n * string. Unknown members are admitted, because a foreign result implementation may add them\n * without changing the published contract.\n *\n * @param value - The value to test\n * @returns True if every published finding member is valid; false otherwise\n */\nexport function isFinding(value: unknown): value is Finding {\n\treturn objectOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tapplied: isBoolean,\n\t\t\tmessage: isString,\n\t\t\tpremises: arrayOf(isPremise),\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Derivation}.\n *\n * @remarks\n * Every published member is checked: `value` follows the published `number` type, including\n * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are\n * admitted, because a foreign result implementation may add them without changing the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published derivation member is valid; false otherwise\n */\nexport function isDerivation(value: unknown): value is Derivation {\n\treturn objectOf({\n\t\tid: isString,\n\t\tvalue: isNumber,\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is an open {@link QualificationResult} returned by a qualifier.\n *\n * @remarks\n * This guard is result-postured for values returned through a borrowed qualifier interface. It\n * checks the complete published result closure — eligibility, scope values, findings,\n * derivations, success, and the audit trail — and admits unknown members and class instances,\n * because a borrowed `QualifierInterface` implementation may add them beyond the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published qualification-result member is valid; false otherwise\n */\nexport function isQualificationResult(value: unknown): value is QualificationResult {\n\treturn objectOf({\n\t\tid: isString,\n\t\tname: isString,\n\t\teligibility: isEligibility,\n\t\tscopes: isEligibilityRecord,\n\t\tfindings: arrayOf(isFinding),\n\t\tderivations: arrayOf(isDerivation),\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).\n *\n * @remarks\n * The guard is exact and requires either complete reason shape, the quantitative definition or\n * the logical one. Nothing is left unchecked, because the reason package owns both authored\n * input shapes.\n *\n * @param value - The value to test\n * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise\n */\nexport function isQualificationPass(value: unknown): value is QualificationPass {\n\treturn isQuantitativeDefinition(value) || isLogicalDefinition(value)\n}\n\n/**\n * Determines whether a value is an exact {@link Ruling} record.\n *\n * @remarks\n * Every authored ruling member and the complete key set are checked. Nothing is left\n * unchecked, because this package owns the authored input record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every ruling member and no unknown key; false otherwise\n */\nexport function isRuling(value: unknown): value is Ruling {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tmessage: isString,\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an exact {@link QualificationDefinition} record.\n *\n * @remarks\n * Every authored definition member, the nested pass and ruling shapes, and the complete key\n * set are checked. Nothing is left unchecked, because this package owns the authored input\n * record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every definition member and no unknown key; false otherwise\n */\nexport function isQualificationDefinition(value: unknown): value is QualificationDefinition {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tname: isString,\n\t\t\tdescription: isString,\n\t\t\tpasses: arrayOf(isQualificationPass),\n\t\t\trulings: arrayOf(isRuling),\n\t\t\tmetadata: isRecord,\n\t\t},\n\t\t['description', 'rulings', 'metadata'],\n\t)(value)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationProjection,\n\tRuling,\n} from './types.js'\nimport type {\n\tCheck,\n\tCheckResult,\n\tEvaluatorInterface,\n\tLogicalDefinition,\n\tLogicalResult,\n\tQuantitativeResult,\n\tReasonResult,\n\tRule,\n\tSubject,\n} from '@orkestrel/reason'\nimport { EFFECT_ELIGIBILITIES, ELIGIBILITY_PRECEDENCE, QUALIFICATION_KEY } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport { isArray, isError, isFiniteNumber, isRecord, resolveField } from '@orkestrel/contract'\nimport {\n\textractAtoms,\n\textractConclusions,\n\tformatField,\n\tisBounds,\n\tisReasonError,\n} from '@orkestrel/reason'\n\n/**\n * Interpolates `{{dotted.path}}` tokens in a message template against a subject.\n *\n * @remarks\n * Each token is split on `.` into a `FieldPath` array and resolved with\n * `resolveField` (a plain string field is one key, never dot-split — the split\n * here is the token-to-path bridge). A finite number renders with `en-US`\n * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.\n * An unresolved path renders as the empty string.\n *\n * @param template - The message template carrying `{{dotted.path}}` tokens\n * @param subject - The record tokens resolve against\n * @returns The template with every token replaced\n *\n * @example\n * ```ts\n * import { interpolateMessage } from '@orkestrel/qualifier'\n *\n * interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'\n * interpolateMessage('Missing {{gone}}', {}) // 'Missing '\n * ```\n */\nexport function interpolateMessage(\n\ttemplate: string,\n\tsubject: Readonly<Record<string, unknown>>,\n): string {\n\treturn template.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_match, path: string) => {\n\t\tconst value = resolveField(subject, path.split('.'))\n\t\tif (value === undefined) return ''\n\t\tif (isFiniteNumber(value)) return value.toLocaleString('en-US')\n\t\treturn String(value)\n\t})\n}\n\n/**\n * Renders a {@link Premise} comparison as a display-neutral verb phrase.\n *\n * @param comparison - The comparison to describe\n * @returns A display-neutral phrase\n *\n * @example\n * ```ts\n * import { renderComparison } from '@orkestrel/qualifier'\n *\n * renderComparison('above') // 'is more than'\n * ```\n */\nexport function renderComparison(comparison: NonNullable<Premise['comparison']>): string {\n\tswitch (comparison) {\n\t\tcase 'equals':\n\t\t\treturn 'is'\n\t\tcase 'not':\n\t\t\treturn 'is not'\n\t\tcase 'above':\n\t\t\treturn 'is more than'\n\t\tcase 'below':\n\t\t\treturn 'is less than'\n\t\tcase 'from':\n\t\t\treturn 'is at least'\n\t\tcase 'to':\n\t\t\treturn 'is at most'\n\t\tcase 'any':\n\t\t\treturn 'is any of'\n\t\tcase 'none':\n\t\t\treturn 'is none of'\n\t\tcase 'between':\n\t\t\treturn 'is between'\n\t\tcase 'outside':\n\t\t\treturn 'is outside'\n\t}\n}\n\n/**\n * Renders a structured or scalar expected value display-neutrally.\n *\n * @remarks\n * An array renders as its elements, each `String`-coerced, joined with `', '`.\n * A reason `Bounds`-shaped record (the `between` / `outside` expected value)\n * renders its present sides joined with the word `and`; an absent side is\n * omitted. A finite number and everything else fall back to plain `String()` —\n * a finite number is not grouped here (grouping is {@link interpolateMessage}'s\n * message-token-only behavior).\n *\n * @param value - The value to render\n * @returns A display-neutral rendering\n *\n * @example\n * ```ts\n * import { renderValue } from '@orkestrel/qualifier'\n *\n * renderValue([18, 25, 40]) // '18, 25, 40'\n * renderValue({ minimum: 18, maximum: 65 }) // '18 and 65'\n * renderValue(42) // '42'\n * ```\n */\nexport function renderValue(value: unknown): string {\n\tif (isArray(value)) return value.map((entry) => String(entry)).join(', ')\n\tif (isBounds(value)) {\n\t\tconst sides: string[] = []\n\t\tif (value.minimum !== undefined) sides.push(String(value.minimum))\n\t\tif (value.maximum !== undefined) sides.push(String(value.maximum))\n\t\treturn sides.join(' and ')\n\t}\n\treturn String(value)\n}\n\n/**\n * Renders one {@link Premise} into a display-neutral sentence.\n *\n * @remarks\n * The checked form renders only when `field` and `comparison` are both\n * present, and `description` then goes unused; a premise missing either half\n * of the checked pair renders as described instead.\n *\n * @param entry - The premise to render\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A display-neutral sentence\n *\n * @example\n * ```ts\n * import { renderPremise } from '@orkestrel/qualifier'\n *\n * renderPremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })\n * // 'age is more than 18 → met'\n *\n * renderPremise({ description: 'Applicant is enrolled', met: true })\n * // 'Applicant is enrolled → met'\n * ```\n */\nexport function renderPremise(entry: Premise, labels?: Readonly<Record<string, string>>): string {\n\tconst status = entry.met === undefined ? 'unknown' : entry.met ? 'met' : 'not met'\n\tif (entry.field === undefined || entry.comparison === undefined) {\n\t\treturn `${entry.description ?? 'Premise'} → ${status}`\n\t}\n\tconst field = formatField(entry.field)\n\tconst label = labels?.[field] ?? entry.label ?? field\n\tconst expected = entry.expected === undefined ? '' : ` ${renderValue(entry.expected)}`\n\treturn `${label} ${renderComparison(entry.comparison)}${expected} → ${status}`\n}\n\n/**\n * Builds a {@link Premise} from an authored {@link Check} and its evaluated\n * `CheckResult`.\n *\n * @param check - The authored check\n * @param result - The evaluated check result carrying the resolved `actual` and `met`\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh premise\n *\n * @example\n * ```ts\n * import { checkToPremise } from '@orkestrel/qualifier'\n * import { createCheck } from '@orkestrel/reason'\n *\n * const authored = createCheck('age', 'above', 18)\n * checkToPremise(authored, { field: 'age', actual: 25, met: true })\n * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }\n * ```\n */\nexport function checkToPremise(\n\tcheck: Check,\n\tresult: CheckResult,\n\tlabels?: Readonly<Record<string, string>>,\n): Premise {\n\tconst field = formatField(check.field)\n\treturn {\n\t\tfield: check.field,\n\t\t...(labels?.[field] === undefined ? {} : { label: labels[field] }),\n\t\tcomparison: check.operator,\n\t\texpected: check.value,\n\t\tactual: result.actual,\n\t\tmet: result.met,\n\t}\n}\n\n/**\n * Builds rich premises for one fired {@link Rule} by walking its premise atoms\n * and re-evaluating each against the working subject.\n *\n * @remarks\n * A reason rule result carries only booleans, so this is the qualifier's own\n * premise-audit projection: each authored premise expression is flattened to its\n * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through\n * the injected `evaluator`. A membership check (`any` / `none`) over an empty\n * array value is content-free and is skipped.\n *\n * @param rule - The authored rule\n * @param working - The working subject to evaluate against\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh, ordered list of rich premises\n *\n * @example\n * ```ts\n * import { ruleToPremises } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const evaluator = createEvaluator()\n * const premises = ruleToPremises(gates.rules[0], { licensed: false }, evaluator)\n *\n * premises[0]?.met // true\n * ```\n */\nexport function ruleToPremises(\n\trule: Rule,\n\tworking: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): readonly Premise[] {\n\tconst output: Premise[] = []\n\tfor (const premise of rule.premises) {\n\t\tfor (const atom of extractAtoms(premise)) {\n\t\t\tconst { check } = atom\n\t\t\tif (\n\t\t\t\t(check.operator === 'any' || check.operator === 'none') &&\n\t\t\t\tisArray(check.value) &&\n\t\t\t\tcheck.value.length === 0\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutput.push(checkToPremise(check, evaluator.evaluate(check, working), labels))\n\t\t}\n\t}\n\treturn output\n}\n\n/**\n * Locates an authored {@link Rule} by id.\n *\n * @param definition - The logical definition to search\n * @param id - The rule id\n * @returns The matching rule, or `undefined`\n *\n * @example\n * ```ts\n * import { findRule } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n *\n * findRule(gates, 'licensed')?.id // 'licensed'\n * ```\n */\nexport function findRule(definition: LogicalDefinition, id: string): Rule | undefined {\n\treturn definition.rules.find((rule) => rule.id === id)\n}\n\n/**\n * Projects one reason result into the internal qualification namespace.\n *\n * @remarks\n * A quantitative result contributes its numeric `value`. A logical result\n * contributes a record of its `conclusion` plus every applied rule's asserted\n * conclusion fields (`extractConclusions`). A mismatched pairing contributes\n * `false`. The projection is pure — it never mutates the subject or the result.\n *\n * @param definition - The pass definition that produced the result\n * @param result - The evaluated reason result\n * @returns The pass's working projection\n *\n * @example\n * ```ts\n * import { reasonResultToProjection } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const result = {\n * reasoning: 'quantitative' as const,\n * value: 500_000,\n * groups: [],\n * count: 1,\n * success: true,\n * trace: [],\n * errors: [],\n * }\n * reasonResultToProjection(cap, result) // 500000\n * ```\n */\nexport function reasonResultToProjection(\n\tdefinition: QualificationPass,\n\tresult: ReasonResult,\n): QualificationProjection {\n\tif (definition.reasoning === 'quantitative' && result.reasoning === 'quantitative') {\n\t\treturn result.value\n\t}\n\tif (definition.reasoning !== 'logical' || result.reasoning !== 'logical') return false\n\tconst projection: Record<string, unknown> = { conclusion: result.conclusion }\n\tfor (const resolved of result.rules) {\n\t\tif (!resolved.applied) continue\n\t\tconst authored = definition.rules.find((rule) => rule.id === resolved.id)\n\t\tif (authored === undefined) continue\n\t\tconst conclusions = extractConclusions(authored.conclusion)\n\t\tfor (const [key, value] of Object.entries(conclusions)) projection[key] = value\n\t}\n\treturn projection\n}\n\n/**\n * Projects a quantitative result into a {@link Derivation} audit record.\n *\n * @param id - The pass id\n * @param result - The evaluated quantitative result\n * @returns A fresh derivation\n *\n * @example\n * ```ts\n * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'\n *\n * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }\n * quantitativeResultToDerivation('cap', result)\n * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }\n * ```\n */\nexport function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation {\n\treturn {\n\t\tid,\n\t\tvalue: result.value,\n\t\tsuccess: result.success,\n\t\ttrace: [...result.trace],\n\t\terrors: [...result.errors],\n\t}\n}\n\n/**\n * Wraps a {@link QualificationContext} under {@link QUALIFICATION_KEY}.\n *\n * @param context - The accumulated projection context\n * @returns A record carrying the context under the reserved key\n *\n * @example\n * ```ts\n * import { qualificationToRecord } from '@orkestrel/qualifier'\n *\n * qualificationToRecord({ cap: 500_000 }) // { qualification: { cap: 500000 } }\n * ```\n */\nexport function qualificationToRecord(\n\tcontext: QualificationContext,\n): Readonly<Record<string, QualificationContext>> {\n\treturn { [QUALIFICATION_KEY]: context }\n}\n\n/**\n * Merges one pass projection into the context, copy-on-write.\n *\n * @param context - The current projection context\n * @param id - The pass id to project under\n * @param projection - The pass's working projection\n * @returns A fresh context with the projection added\n *\n * @example\n * ```ts\n * import { mergeQualificationContext } from '@orkestrel/qualifier'\n *\n * mergeQualificationContext({ cap: 500_000 }, 'excess', 250_000)\n * // { cap: 500000, excess: 250000 }\n * ```\n */\nexport function mergeQualificationContext(\n\tcontext: QualificationContext,\n\tid: string,\n\tprojection: QualificationProjection,\n): QualificationContext {\n\treturn { ...context, [id]: projection }\n}\n\n/**\n * Joins a ruling, its logical rule result, the pass, the pre-projection subject,\n * and an evaluator into a {@link Finding}.\n *\n * @remarks\n * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated\n * against the same subject snapshot the pass evaluated, so a conclusion the pass\n * derived can never satisfy its own reported premise. The message interpolates\n * against that snapshot.\n *\n * @param ruling - The authored ruling\n * @param pass - The pass the ruling's rule lives in\n * @param result - The pass's evaluated logical result\n * @param subject - The pre-projection subject snapshot the pass evaluated\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh finding\n *\n * @example\n * ```ts\n * import { rulingToFinding } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }\n * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true] }], count: 1, success: true, trace: [], errors: [] }\n * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())\n *\n * finding.applied // true\n * ```\n */\nexport function rulingToFinding(\n\truling: Ruling,\n\tpass: QualificationPass,\n\tresult: LogicalResult,\n\tsubject: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): Finding {\n\tconst entry = result.rules.find((resolved) => resolved.id === ruling.rule)\n\tconst applied = entry?.applied ?? false\n\tconst rule = pass.reasoning === 'logical' ? findRule(pass, ruling.rule) : undefined\n\tconst premises = rule === undefined ? [] : ruleToPremises(rule, subject, evaluator, labels)\n\treturn {\n\t\tid: ruling.id,\n\t\tpass: ruling.pass,\n\t\trule: ruling.rule,\n\t\teffect: ruling.effect,\n\t\t...(ruling.scope === undefined ? {} : { scope: ruling.scope }),\n\t\tapplied,\n\t\t...(ruling.message === undefined\n\t\t\t? {}\n\t\t\t: { message: interpolateMessage(ruling.message, subject) }),\n\t\tpremises,\n\t}\n}\n\n/**\n * Derives global eligibility from applied, unscoped findings.\n *\n * @remarks\n * Only findings that both applied and carry no `scope` shape global eligibility.\n * A failed pass contributes a synthetic `referral` (fail-closed), passed through\n * `failed`.\n *\n * @param findings - The resolved findings\n * @param failed - If `true`, adds a synthetic `referral` impact so an operational failure\n * fails closed; if `false`, derives eligibility from the findings alone. Default: `false`\n * @returns The most severe global eligibility\n *\n * @example\n * ```ts\n * import { deriveFindingEligibility } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const, applied: true, premises: [] }\n * deriveFindingEligibility([finding]) // 'ineligible'\n * ```\n */\nexport function deriveFindingEligibility(\n\tfindings: readonly Finding[],\n\tfailed = false,\n): Eligibility {\n\tconst eligibilities = findings\n\t\t.filter((finding) => finding.applied && finding.scope === undefined)\n\t\t.map((finding) => EFFECT_ELIGIBILITIES[finding.effect])\n\tif (failed) eligibilities.push('referral')\n\treturn combineEligibilities(eligibilities)\n}\n\n/**\n * Returns the most severe {@link Eligibility} in a list.\n *\n * @param eligibilities - The eligibilities to combine\n * @returns The most severe eligibility, or `'eligible'` for an empty list\n *\n * @example\n * ```ts\n * import { combineEligibilities } from '@orkestrel/qualifier'\n *\n * combineEligibilities(['eligible', 'referral']) // 'referral'\n * ```\n */\nexport function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility {\n\tfor (const eligibility of ELIGIBILITY_PRECEDENCE) {\n\t\tif (eligibilities.includes(eligibility)) return eligibility\n\t}\n\treturn 'eligible'\n}\n\n/**\n * Derives one eligibility per finding scope.\n *\n * @remarks\n * Only applied, scoped findings contribute. Each named scope combines its own\n * findings' impacts by severity. An unmentioned scope is absent (eligible by\n * default at the program layer).\n *\n * @param findings - The resolved findings\n * @returns A fresh record of scope to its combined eligibility\n *\n * @example\n * ```ts\n * import { deriveScopeEligibilities } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'coastal', pass: 'wind', rule: 'coastal', effect: 'restriction' as const, scope: 'wind', applied: true, premises: [] }\n * deriveScopeEligibilities([finding]) // { wind: 'ineligible' }\n * ```\n */\nexport function deriveScopeEligibilities(\n\tfindings: readonly Finding[],\n): Readonly<Record<string, Eligibility>> {\n\tconst grouped: Record<string, Eligibility[]> = {}\n\tfor (const finding of findings) {\n\t\tif (!finding.applied || finding.scope === undefined) continue\n\t\tconst current = grouped[finding.scope] ?? []\n\t\tgrouped[finding.scope] = [...current, EFFECT_ELIGIBILITIES[finding.effect]]\n\t}\n\tconst scopes: Record<string, Eligibility> = {}\n\tfor (const [scope, eligibilities] of Object.entries(grouped)) {\n\t\tscopes[scope] = combineEligibilities(eligibilities)\n\t}\n\treturn scopes\n}\n\n/**\n * Describes each ruling whose pass or rule does not exist or whose pass is not\n * logical, and each pass id shadowing the reserved {@link QUALIFICATION_KEY}.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of reference-error messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, createRuling, describeMissingReferences } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates], {\n * rulings: [createRuling('license', 'gates', 'absent', 'restriction')],\n * })\n *\n * describeMissingReferences(definition)\n * // [\"Ruling 'license' references missing rule 'absent' in pass 'gates'\"]\n * ```\n */\nexport function describeMissingReferences(definition: QualificationDefinition): readonly string[] {\n\tconst errors: string[] = []\n\tconst passes = new Map(definition.passes.map((pass) => [pass.id, pass]))\n\tfor (const pass of definition.passes) {\n\t\tif (pass.id === QUALIFICATION_KEY) {\n\t\t\terrors.push(`Pass id must not equal reserved key '${QUALIFICATION_KEY}'`)\n\t\t}\n\t}\n\tfor (const ruling of definition.rulings ?? []) {\n\t\tconst pass = passes.get(ruling.pass)\n\t\tif (pass === undefined) {\n\t\t\terrors.push(`Ruling '${ruling.id}' references missing pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (pass.reasoning !== 'logical') {\n\t\t\terrors.push(`Ruling '${ruling.id}' references non-logical pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (findRule(pass, ruling.rule) === undefined) {\n\t\t\terrors.push(\n\t\t\t\t`Ruling '${ruling.id}' references missing rule '${ruling.rule}' in pass '${ruling.pass}'`,\n\t\t\t)\n\t\t}\n\t}\n\treturn errors\n}\n\n/**\n * Determines whether a subject already owns the reserved {@link QUALIFICATION_KEY}.\n *\n * @param subject - The subject to check\n * @returns True if the subject owns the reserved key; false otherwise\n *\n * @example\n * ```ts\n * import { hasReservedKey } from '@orkestrel/qualifier'\n *\n * hasReservedKey({ id: 's1', qualification: {} }) // true\n * ```\n */\nexport function hasReservedKey(subject: Subject): boolean {\n\treturn Object.hasOwn(subject, QUALIFICATION_KEY)\n}\n\n/**\n * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.\n *\n * @param value - The candidate subject to validate\n * @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or\n * when it already carries the reserved `qualification` key\n *\n * @example\n * ```ts\n * import { assertSubject } from '@orkestrel/qualifier'\n *\n * assertSubject({ id: 's1' }) // narrows to Subject\n * ```\n */\nexport function assertSubject(value: unknown): asserts value is Subject {\n\tif (!isRecord(value)) {\n\t\tthrow new QualifierError('MISMATCH', 'Qualification subject must be a record')\n\t}\n\tif (hasReservedKey(value)) {\n\t\tthrow new QualifierError(\n\t\t\t'MISMATCH',\n\t\t\t`Qualification subject must not contain reserved key '${QUALIFICATION_KEY}'`,\n\t\t)\n\t}\n}\n\n/**\n * Maps an engine throw caught while running one pass to a typed {@link QualifierError}.\n *\n * @remarks\n * A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the\n * pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;\n * every other `ReasonError` code (for example `MISSING`) and every non-`ReasonError` throw\n * (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw\n * is preserved as `context.cause`.\n *\n * @param error - The value caught from the engine's `reason` call\n * @param pass - The id of the pass that was running\n * @returns A fresh, typed {@link QualifierError}\n *\n * @example\n * ```ts\n * import { mapEngineError } from '@orkestrel/qualifier'\n * import { ReasonError } from '@orkestrel/reason'\n *\n * mapEngineError(new ReasonError('MISSING', 'No reasoner registered'), 'cap').code // 'ENGINE'\n * ```\n */\nexport function mapEngineError(error: unknown, pass: string): QualifierError {\n\tif (isReasonError(error)) {\n\t\tif (error.code === 'INVALID') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DEFINITION',\n\t\t\t\t`Pass '${pass}' failed engine validation: ${error.message}`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\tif (error.code === 'DESTROYED') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DESTROYED',\n\t\t\t\t`Qualifier engine was destroyed during pass '${pass}'`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${error.message}`, {\n\t\t\tpass,\n\t\t\tcause: error,\n\t\t})\n\t}\n\tconst message = isError(error) ? error.message : String(error)\n\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${message}`, {\n\t\tpass,\n\t\tcause: error,\n\t})\n}\n\n/**\n * Describes each logical pass carrying no rulings.\n *\n * @remarks\n * A logical pass with no matching ruling can never contribute a finding, so its\n * rules only ever influence later passes' reads — this is very likely an authoring\n * oversight.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeEmptyLogicalPasses } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates])\n *\n * describeEmptyLogicalPasses(definition) // [\"Logical pass 'gates' has no rulings\"]\n * ```\n */\nexport function describeEmptyLogicalPasses(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tfor (const pass of definition.passes) {\n\t\tif (pass.reasoning !== 'logical') continue\n\t\tconst hasRuling = (definition.rulings ?? []).some((ruling) => ruling.pass === pass.id)\n\t\tif (!hasRuling) warnings.push(`Logical pass '${pass.id}' has no rulings`)\n\t}\n\treturn warnings\n}\n\n/**\n * Describes each quantitative pass never read by a later pass.\n *\n * @remarks\n * A quantitative pass's projected value lives under `qualification.<id>` — if no\n * later logical premise or quantitative factor field reads that key, the\n * derivation is dead weight. Reads are collected per later pass: a logical pass's\n * premise atoms (conclusion atoms are writes, not reads), and a quantitative\n * pass's field/lookup/range factor sources plus factor checks.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeUnreadDerivations } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [cap])\n *\n * describeUnreadDerivations(definition) // [\"Quantitative pass 'cap' is never read by a later pass\"]\n * ```\n */\nexport function describeUnreadDerivations(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tdefinition.passes.forEach((pass, index) => {\n\t\tif (pass.reasoning !== 'quantitative') return\n\t\tconst key = formatField(['qualification', pass.id])\n\t\tconst reads = new Set<string>()\n\t\tfor (const later of definition.passes.slice(index + 1)) {\n\t\t\tif (later.reasoning === 'logical') {\n\t\t\t\tfor (const rule of later.rules) {\n\t\t\t\t\tfor (const premise of rule.premises) {\n\t\t\t\t\t\tfor (const atom of extractAtoms(premise)) reads.add(formatField(atom.check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (later.reasoning === 'quantitative') {\n\t\t\t\tfor (const group of later.groups) {\n\t\t\t\t\tfor (const factor of group.factors) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tfactor.source.origin === 'field' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'lookup' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'range'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treads.add(formatField(factor.source.field))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const check of factor.checks ?? []) reads.add(formatField(check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!reads.has(key)) {\n\t\t\twarnings.push(`Quantitative pass '${pass.id}' is never read by a later pass`)\n\t\t}\n\t})\n\treturn warnings\n}\n","import type {\n\tDerivation,\n\tFinding,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationResult,\n\tQualifierEventMap,\n\tQualifierInterface,\n\tQualifierOptions,\n} from './types.js'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tEvaluatorInterface,\n\tReasonInterface,\n\tReasonResult,\n\tReasonValidationResult,\n\tSubject,\n} from '@orkestrel/reason'\nimport { DEFAULT_QUALIFIER_VALIDATE } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport {\n\tassertSubject,\n\tdescribeEmptyLogicalPasses,\n\tdescribeMissingReferences,\n\tdescribeUnreadDerivations,\n\tderiveFindingEligibility,\n\tderiveScopeEligibilities,\n\tmapEngineError,\n\tmergeQualificationContext,\n\tqualificationToRecord,\n\tquantitativeResultToDerivation,\n\treasonResultToProjection,\n\trulingToFinding,\n} from './helpers.js'\nimport { isQualificationPass, isRuling } from './validators.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcreateEvaluator,\n\tcreateLogicalReasoner,\n\tcreateQuantitativeReasoner,\n\tcreateReason,\n\tfindDuplicates,\n} from '@orkestrel/reason'\n\n/**\n * Runs ordered passes over one reason engine and returns eligibility.\n *\n * @remarks\n * The engine is owned when self-created (destroyed on `destroy()`) and borrowed\n * when injected (never destroyed). Semantic validation runs before the first pass\n * whenever the `validate` option is on. `qualify` builds its working subject through\n * copy-on-write overlays only — the caller's `subject` is never mutated, and the\n * qualifier's internal projection is discarded after each call.\n */\nexport class Qualifier implements QualifierInterface {\n\treadonly #emitter: Emitter<QualifierEventMap>\n\treadonly #engine: ReasonInterface\n\treadonly #evaluator: EvaluatorInterface\n\treadonly #owned: boolean\n\treadonly #validate: boolean\n\treadonly #labels: Readonly<Record<string, string>> | undefined\n\t#destroyed = false\n\n\tconstructor(options?: QualifierOptions) {\n\t\tthis.#emitter = new Emitter({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\tthis.#evaluator = createEvaluator()\n\t\tthis.#owned = options?.engine === undefined\n\t\tthis.#engine =\n\t\t\toptions?.engine ??\n\t\t\tcreateReason({\n\t\t\t\treasoners: [\n\t\t\t\t\tcreateQuantitativeReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t\tcreateLogicalReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t],\n\t\t\t\tbail: false,\n\t\t\t})\n\t\tthis.#validate = options?.validate ?? DEFAULT_QUALIFIER_VALIDATE\n\t\tthis.#labels = options?.labels\n\t}\n\n\t/**\n\t * Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and\n\t * `destroy`.\n\t *\n\t * @returns The emitter this qualifier owns\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.emitter.on('qualify', (result) => console.log(result.eligibility))\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tget emitter(): EmitterInterface<QualifierEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/**\n\t * Qualifies one subject against one authored definition.\n\t *\n\t * @remarks\n\t * Semantic validation runs first when the `validate` option is on, so an invalid\n\t * definition throws before any pass runs. Passes run in authored order over a\n\t * copy-on-write working subject, and the caller's `subject` is never mutated. An\n\t * unscoped restriction stops the remaining passes because no later finding can be\n\t * more severe.\n\t *\n\t * @param subject - The record to qualify\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh, frozen qualification result\n\t * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the\n\t * definition fails semantic validation, or when the engine rejects a pass as an invalid\n\t * definition; `'MISMATCH'` when the subject is not a record or already carries the\n\t * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine\n\t * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, createRuling, Qualifier } from '@orkestrel/qualifier'\n\t * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n\t *\n\t * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n\t * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n\t * ])\n\t * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {\n\t * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],\n\t * })\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tqualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tthis.#alive()\n\t\tif (this.#validate) {\n\t\t\tconst validation = this.validate(definition)\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new QualifierError(\n\t\t\t\t\t'DEFINITION',\n\t\t\t\t\t`Qualification definition is invalid: ${validation.errors.join(', ')}`,\n\t\t\t\t\t{ definition: definition.id },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn this.#qualify(subject, definition)\n\t}\n\n\t/**\n\t * Validates one authored definition semantically, without running it.\n\t *\n\t * @remarks\n\t * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:\n\t * non-empty id and name, valid and uniquely identified passes and rulings, every\n\t * ruling reference resolving to a logical pass and one of its rules, and no pass id\n\t * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no\n\t * ruling, and a derivation no later pass reads are warnings rather than errors.\n\t *\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`\n\t * @throws {@link QualifierError} `'DESTROYED'` after `destroy`\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))\n\t * // { valid: true, errors: [], warnings: ['Definition has no passes'] }\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tvalidate(definition: QualificationDefinition): ReasonValidationResult {\n\t\tthis.#alive()\n\t\tconst errors: string[] = []\n\t\tconst warnings: string[] = []\n\t\tif (definition.id.length === 0) errors.push('Definition id must not be empty')\n\t\tif (definition.name.length === 0) errors.push('Definition name must not be empty')\n\t\tdefinition.passes.forEach((pass, index) => {\n\t\t\tif (!isQualificationPass(pass)) {\n\t\t\t\terrors.push(`Pass at index ${index} is not a valid qualification pass`)\n\t\t\t}\n\t\t})\n\t\t;(definition.rulings ?? []).forEach((ruling, index) => {\n\t\t\tif (!isRuling(ruling)) errors.push(`Ruling at index ${index} is not a valid ruling`)\n\t\t})\n\t\tfor (const id of findDuplicates(definition.passes)) errors.push(`Duplicate pass id '${id}'`)\n\t\tfor (const id of findDuplicates(definition.rulings ?? [])) {\n\t\t\terrors.push(`Duplicate ruling id '${id}'`)\n\t\t}\n\t\tfor (const reference of describeMissingReferences(definition)) errors.push(reference)\n\t\tif (definition.passes.length === 0) warnings.push('Definition has no passes')\n\t\tfor (const warning of describeEmptyLogicalPasses(definition)) warnings.push(warning)\n\t\tfor (const warning of describeUnreadDerivations(definition)) warnings.push(warning)\n\t\treturn { valid: errors.length === 0, errors, warnings }\n\t}\n\n\t/**\n\t * Destroys this qualifier, idempotently.\n\t *\n\t * @remarks\n\t * An engine this qualifier created is destroyed; an injected engine stays\n\t * caller-owned and is left alone. The `destroy` event fires before the emitter\n\t * itself is destroyed, and a later call returns without re-emitting.\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.destroy()\n\t * qualifier.destroy() // idempotent\n\t * ```\n\t */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tif (this.#owned) this.#engine.destroy()\n\t\tthis.#destroyed = true\n\t\tthis.#emitter.emit('destroy')\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#qualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tassertSubject(subject)\n\t\tlet context: QualificationContext = {}\n\t\tlet working: Subject = { ...subject }\n\t\tconst findings: Finding[] = []\n\t\tconst derivations: Derivation[] = []\n\t\tconst trace: string[] = []\n\t\tconst errors: string[] = []\n\t\tlet failed = false\n\n\t\tfor (const pass of definition.passes) {\n\t\t\tthis.#alive()\n\t\t\tconst evaluated = working\n\t\t\tlet result: ReasonResult\n\t\t\ttry {\n\t\t\t\tresult = this.#engine.reason(evaluated, pass)\n\t\t\t} catch (error) {\n\t\t\t\tthrow mapEngineError(error, pass.id)\n\t\t\t}\n\n\t\t\ttrace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`))\n\t\t\terrors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`))\n\n\t\t\tif (result.reasoning === 'quantitative') {\n\t\t\t\tconst derivation = quantitativeResultToDerivation(pass.id, result)\n\t\t\t\tderivations.push(derivation)\n\t\t\t\tthis.#emitter.emit('derive', derivation)\n\t\t\t}\n\n\t\t\tif (result.reasoning === 'logical') {\n\t\t\t\tconst rulings = (definition.rulings ?? []).filter((ruling) => ruling.pass === pass.id)\n\t\t\t\tfor (const ruling of rulings) {\n\t\t\t\t\tconst finding = rulingToFinding(\n\t\t\t\t\t\truling,\n\t\t\t\t\t\tpass,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tevaluated,\n\t\t\t\t\t\tthis.#evaluator,\n\t\t\t\t\t\tthis.#labels,\n\t\t\t\t\t)\n\t\t\t\t\tfindings.push(finding)\n\t\t\t\t\tthis.#emitter.emit('finding', finding)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst projection = reasonResultToProjection(pass, result)\n\t\t\tcontext = mergeQualificationContext(context, pass.id, projection)\n\t\t\tworking = { ...subject, ...qualificationToRecord(context) }\n\n\t\t\tif (!result.success) {\n\t\t\t\tfailed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif (deriveFindingEligibility(findings) === 'ineligible') break\n\t\t}\n\n\t\tconst success = !failed && errors.length === 0\n\t\tconst scopes = deriveScopeEligibilities(findings)\n\t\tconst qualification: QualificationResult = Object.freeze({\n\t\t\tid: definition.id,\n\t\t\tname: definition.name,\n\t\t\teligibility: deriveFindingEligibility(findings, !success),\n\t\t\tscopes: Object.freeze(scopes),\n\t\t\tfindings: Object.freeze(findings),\n\t\t\tderivations: Object.freeze(derivations),\n\t\t\tsuccess,\n\t\t\ttrace: Object.freeze(trace),\n\t\t\terrors: Object.freeze(errors),\n\t\t})\n\t\tthis.#emitter.emit('qualify', qualification)\n\t\treturn qualification\n\t}\n\n\t#alive(): void {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new QualifierError('DESTROYED', 'Qualifier has been destroyed')\n\t\t}\n\t}\n}\n","import type {\n\tQualificationDefinition,\n\tQualificationEffect,\n\tQualificationInput,\n\tQualificationPass,\n\tQualifierInterface,\n\tQualifierOptions,\n\tRuling,\n\tRulingInput,\n} from './types.js'\nimport { Qualifier } from './Qualifier.js'\n\n/**\n * Creates one {@link QualifierInterface} over a reason engine.\n *\n * @remarks\n * A standalone qualifier creates and owns one shared quantitative-plus-logical\n * reason engine, destroying it on `destroy()`. An injected `options.engine`\n * remains caller-owned and is never destroyed. `validate` runs semantic\n * validation before every qualification. Default: `true`. `labels` overrides the\n * display name of a premise field, keyed by the dot-joined field path\n * `formatField` produces, and takes precedence over a premise's own `label`.\n *\n * @param options - Optional injected engine, validation, labels, and emitter hooks\n * @returns A {@link QualifierInterface}\n *\n * @example Create a qualifier\n * ```ts\n * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n * \tcreateRule(\n * \t\t'licensed',\n * \t\t[createAtom('licensed', 'equals', false)],\n * \t\tcreateAtom('blocked', 'equals', true),\n * \t),\n * ])\n *\n * const bare = createRuling('license', 'gates', 'licensed', 'restriction')\n * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {\n * \tmessage: 'A license is required',\n * })\n *\n * 'message' in bare // false — an absent optional key is omitted, never written as undefined\n * messaged.message // 'A license is required'\n *\n * const passes = [gates]\n * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {\n * \trulings: [messaged],\n * })\n *\n * 'description' in definition // false\n * definition.passes === passes // false — the factory copies what it is handed\n *\n * const qualifier = createQualifier()\n * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)\n * qualifier.destroy()\n * ```\n */\nexport function createQualifier(options?: QualifierOptions): QualifierInterface {\n\treturn new Qualifier(options)\n}\n\n/**\n * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.\n *\n * @remarks\n * `passes` and `rulings` arrays are copied, and record `metadata` is\n * shallow-copied so nested values are not deep-cloned.\n *\n * @param id - The definition id\n * @param name - The display name\n * @param passes - The ordered qualification passes\n * @param input - Optional description, rulings, and metadata\n * @returns A fresh qualification definition\n *\n * @example\n * ```ts\n * import { createQualificationDefinition } from '@orkestrel/qualifier'\n *\n * createQualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })\n * ```\n */\nexport function createQualificationDefinition(\n\tid: string,\n\tname: string,\n\tpasses: readonly QualificationPass[],\n\tinput?: QualificationInput,\n): QualificationDefinition {\n\treturn {\n\t\tid,\n\t\tname,\n\t\tpasses: [...passes],\n\t\t...(input?.description === undefined ? {} : { description: input.description }),\n\t\t...(input?.rulings === undefined ? {} : { rulings: [...input.rulings] }),\n\t\t...(input?.metadata === undefined ? {} : { metadata: { ...input.metadata } }),\n\t}\n}\n\n/**\n * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.\n *\n * @param id - The ruling id\n * @param pass - The logical pass id the rule lives in\n * @param rule - The rule id whose firing this ruling reacts to\n * @param effect - The eligibility impact when the rule fires\n * @param input - Optional selection scope and message template\n * @returns A fresh ruling\n *\n * @example\n * ```ts\n * import { createRuling } from '@orkestrel/qualifier'\n *\n * createRuling('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })\n * ```\n */\nexport function createRuling(\n\tid: string,\n\tpass: string,\n\trule: string,\n\teffect: QualificationEffect,\n\tinput?: RulingInput,\n): Ruling {\n\treturn {\n\t\tid,\n\t\tpass,\n\t\trule,\n\t\teffect,\n\t\t...(input?.scope === undefined ? {} : { scope: input.scope }),\n\t\t...(input?.message === undefined ? {} : { message: input.message }),\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Qualifier.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { Eligibility, QualificationEffect } from './types.js'\n\n/**\n * Holds the default definition validation policy for `createQualifier` and\n * `Qualifier.qualify`, `true`.\n */\nexport const DEFAULT_QUALIFIER_VALIDATE = true\n\n/**\n * Names `'qualification'`, the reserved internal projection namespace a pass's working\n * projection is written under.\n */\nexport const QUALIFICATION_KEY = 'qualification'\n\n/** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */\nexport const ELIGIBILITY_PRECEDENCE: readonly Eligibility[] = Object.freeze([\n\t'ineligible',\n\t'referral',\n\t'eligible',\n])\n\n/**\n * Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to\n * `ineligible`, `referral` to `referral`, and `condition` to `eligible`.\n */\nexport const EFFECT_ELIGIBILITIES: Readonly<Record<QualificationEffect, Eligibility>> =\n\tObject.freeze({\n\t\trestriction: 'ineligible',\n\t\treferral: 'referral',\n\t\tcondition: 'eligible',\n\t})\n","import type { QualifierErrorCode, QualifierErrorContext } from './types.js'\nimport { holds, INTRINSICS, isFunction, isObject } from '@orkestrel/contract'\n\n/**\n * Represents a coded programmer error thrown by the qualifier layer, carrying a\n * `QualifierErrorCode` and an optional context record.\n *\n * @remarks\n * `DEFINITION` — a qualification definition failed semantic validation; when\n * `qualify`'s own validation raises it, `context.definition` names the\n * definition, and when an engine throw maps to it, `context.pass` names the pass\n * and `context.cause` preserves the original throw. `MISMATCH` — a subject is\n * not a record or already carries the reserved `qualification` key.\n * `DESTROYED` — use of a destroyed qualifier; an engine-mapped one carries\n * `context.pass` and `context.cause`. `ENGINE` — the underlying reason engine\n * threw while running a pass; `context.pass` names the pass and `context.cause`\n * preserves the original throw. `context` is absent for a throw that carries no\n * payload.\n */\nexport class QualifierError extends Error {\n\treadonly code: QualifierErrorCode\n\treadonly context: QualifierErrorContext | undefined\n\n\tconstructor(code: QualifierErrorCode, message: string, context?: QualifierErrorContext) {\n\t\tsuper(message)\n\t\tINTRINSICS.define(this, Symbol.for('@orkestrel/qualifier.QualifierError'), { value: this })\n\t\tthis.name = 'QualifierError'\n\t\tthis.code = code\n\t\tthis.context = context\n\t}\n}\n\n/**\n * Narrows a caught value to a {@link QualifierError}.\n *\n * @param value - The caught value to test\n * @returns True if the error carries its own identity brand and declared members; false otherwise\n */\nexport function isQualifierError(value: unknown): value is QualifierError {\n\treturn holds(() => {\n\t\tif (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false\n\t\tconst brand = INTRINSICS.describe(value, Symbol.for('@orkestrel/qualifier.QualifierError'))\n\t\tif (brand?.value !== value) return false\n\t\tconst code: unknown = INTRINSICS.reflect.read(value, 'code')\n\t\tif (code !== 'DEFINITION' && code !== 'MISMATCH' && code !== 'DESTROYED' && code !== 'ENGINE')\n\t\t\treturn false\n\t\tconst context: unknown = INTRINSICS.reflect.read(value, 'context')\n\t\tif (context === undefined) return true\n\t\tif (!isObject(context) && !isFunction(context)) return false\n\t\tconst pass: unknown = INTRINSICS.reflect.read(context, 'pass')\n\t\tconst definition: unknown = INTRINSICS.reflect.read(context, 'definition')\n\t\treturn (\n\t\t\t(pass === undefined || typeof pass === 'string') &&\n\t\t\t(definition === undefined || typeof definition === 'string')\n\t\t)\n\t})\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tRuling,\n} from './types.js'\nimport {\n\tarrayOf,\n\tisBoolean,\n\tisNumber,\n\tisRecord,\n\tisString,\n\tliteralOf,\n\tobjectOf,\n\trecordOf,\n\twhereOf,\n} from '@orkestrel/contract'\nimport {\n\tisComparison,\n\tisFieldPath,\n\tisLogicalDefinition,\n\tisQuantitativeDefinition,\n} from '@orkestrel/reason'\n\n/**\n * Determines whether a value is an {@link Eligibility} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the eligibility literals; false otherwise\n */\nexport const isEligibility = literalOf('eligible', 'ineligible', 'referral')\n\n/**\n * Determines whether a value is a {@link QualificationEffect} literal.\n *\n * @remarks\n * The guard is total and leaves nothing unchecked: a scalar union has no open or exact\n * axis.\n *\n * @param value - The value to test\n * @returns True if `value` is one of the effect literals; false otherwise\n */\nexport const isQualificationEffect = literalOf('restriction', 'referral', 'condition')\n\n/**\n * Determines whether a value is an open string-keyed record of {@link Eligibility} values.\n *\n * @remarks\n * Every own string-named value is checked through {@link isEligibility}, including\n * non-enumerable values. Inherited and symbol-named members are left unchecked because they\n * are outside the own string-keyed record this guard certifies.\n *\n * @param value - The value to test\n * @returns True if `value` is a non-array object whose own string-named values are\n * eligibilities; false otherwise\n */\nexport function isEligibilityRecord(\n\tvalue: unknown,\n): value is Readonly<Record<string, Eligibility>> {\n\treturn whereOf(objectOf({}), (record): record is Readonly<Record<string, Eligibility>> =>\n\t\tObject.getOwnPropertyNames(record).every((key) => isEligibility(Reflect.get(record, key))),\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Premise}.\n *\n * @remarks\n * The optional `field`, `label`, `description`, `comparison`, and `met` members are checked\n * when they are defined. `expected` is unchecked and may be absent or contain any value\n * because its published type is `unknown`. `actual` is unchecked and may be absent or contain\n * any value for the same reason. Unknown members are admitted, because an open result admits\n * what the published contract does not name.\n *\n * @param value - The value to test\n * @returns True if every checked premise member follows its published type; false otherwise\n */\nexport function isPremise(value: unknown): value is Premise {\n\treturn objectOf(\n\t\t{\n\t\t\tfield: isFieldPath,\n\t\t\tlabel: isString,\n\t\t\tdescription: isString,\n\t\t\tcomparison: isComparison,\n\t\t\tmet: isBoolean,\n\t\t},\n\t\t['field', 'label', 'description', 'comparison', 'met'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Finding}.\n *\n * @remarks\n * Every published member is checked, including each nested {@link Premise}. Optional `scope`\n * and `message` members may be absent or `undefined`; a present defined value must be a\n * string. Unknown members are admitted, because a foreign result implementation may add them\n * without changing the published contract.\n *\n * @param value - The value to test\n * @returns True if every published finding member is valid; false otherwise\n */\nexport function isFinding(value: unknown): value is Finding {\n\treturn objectOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tapplied: isBoolean,\n\t\t\tmessage: isString,\n\t\t\tpremises: arrayOf(isPremise),\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an open result-side {@link Derivation}.\n *\n * @remarks\n * Every published member is checked: `value` follows the published `number` type, including\n * `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are\n * admitted, because a foreign result implementation may add them without changing the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published derivation member is valid; false otherwise\n */\nexport function isDerivation(value: unknown): value is Derivation {\n\treturn objectOf({\n\t\tid: isString,\n\t\tvalue: isNumber,\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is an open {@link QualificationResult} returned by a qualifier.\n *\n * @remarks\n * This guard is result-postured for values returned through a borrowed qualifier interface. It\n * checks the complete published result closure — eligibility, scope values, findings,\n * derivations, success, and the audit trail — and admits unknown members and class instances,\n * because a borrowed `QualifierInterface` implementation may add them beyond the\n * published contract.\n *\n * @param value - The value to test\n * @returns True if every published qualification-result member is valid; false otherwise\n */\nexport function isQualificationResult(value: unknown): value is QualificationResult {\n\treturn objectOf({\n\t\tid: isString,\n\t\tname: isString,\n\t\teligibility: isEligibility,\n\t\tscopes: isEligibilityRecord,\n\t\tfindings: arrayOf(isFinding),\n\t\tderivations: arrayOf(isDerivation),\n\t\tsuccess: isBoolean,\n\t\ttrace: arrayOf(isString),\n\t\terrors: arrayOf(isString),\n\t})(value)\n}\n\n/**\n * Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).\n *\n * @remarks\n * The guard is exact and requires either complete reason shape, the quantitative definition or\n * the logical one. Nothing is left unchecked, because the reason package owns both authored\n * input shapes.\n *\n * @param value - The value to test\n * @returns True if `value` is a complete reason quantitative or logical definition; false otherwise\n */\nexport function isQualificationPass(value: unknown): value is QualificationPass {\n\treturn isQuantitativeDefinition(value) || isLogicalDefinition(value)\n}\n\n/**\n * Determines whether a value is an exact {@link Ruling} record.\n *\n * @remarks\n * Every authored ruling member and the complete key set are checked. Nothing is left\n * unchecked, because this package owns the authored input record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every ruling member and no unknown key; false otherwise\n */\nexport function isRuling(value: unknown): value is Ruling {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tpass: isString,\n\t\t\trule: isString,\n\t\t\teffect: isQualificationEffect,\n\t\t\tscope: isString,\n\t\t\tmessage: isString,\n\t\t},\n\t\t['scope', 'message'],\n\t)(value)\n}\n\n/**\n * Determines whether a value is an exact {@link QualificationDefinition} record.\n *\n * @remarks\n * Every authored definition member, the nested pass and ruling shapes, and the complete key\n * set are checked. Nothing is left unchecked, because this package owns the authored input\n * record.\n *\n * @param value - The value to test\n * @returns True if `value` carries every definition member and no unknown key; false otherwise\n */\nexport function isQualificationDefinition(value: unknown): value is QualificationDefinition {\n\treturn recordOf(\n\t\t{\n\t\t\tid: isString,\n\t\t\tname: isString,\n\t\t\tdescription: isString,\n\t\t\tpasses: arrayOf(isQualificationPass),\n\t\t\trulings: arrayOf(isRuling),\n\t\t\tmetadata: isRecord,\n\t\t},\n\t\t['description', 'rulings', 'metadata'],\n\t)(value)\n}\n","import type {\n\tDerivation,\n\tEligibility,\n\tFinding,\n\tPremise,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationProjection,\n\tRuling,\n} from './types.js'\nimport type {\n\tCheck,\n\tCheckResult,\n\tEvaluatorInterface,\n\tLogicalDefinition,\n\tLogicalResult,\n\tQuantitativeResult,\n\tReasonResult,\n\tRule,\n\tSubject,\n} from '@orkestrel/reason'\nimport { EFFECT_ELIGIBILITIES, ELIGIBILITY_PRECEDENCE, QUALIFICATION_KEY } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport { isArray, isError, isFiniteNumber, isRecord, resolveField } from '@orkestrel/contract'\nimport {\n\textractAtoms,\n\textractConclusions,\n\tformatField,\n\tisBounds,\n\tisReasonError,\n} from '@orkestrel/reason'\n\n/**\n * Interpolates `{{dotted.path}}` tokens in a message template against a subject.\n *\n * @remarks\n * Each token is split on `.` into a `FieldPath` array and resolved with\n * `resolveField` (a plain string field is one key, never dot-split — the split\n * here is the token-to-path bridge). A finite number renders with `en-US`\n * thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.\n * An unresolved path renders as the empty string.\n *\n * @param template - The message template carrying `{{dotted.path}}` tokens\n * @param subject - The record tokens resolve against\n * @returns The template with every token replaced\n *\n * @example\n * ```ts\n * import { interpolateMessage } from '@orkestrel/qualifier'\n *\n * interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'\n * interpolateMessage('Missing {{gone}}', {}) // 'Missing '\n * ```\n */\nexport function interpolateMessage(\n\ttemplate: string,\n\tsubject: Readonly<Record<string, unknown>>,\n): string {\n\treturn template.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_match, path: string) => {\n\t\tconst value = resolveField(subject, path.split('.'))\n\t\tif (value === undefined) return ''\n\t\tif (isFiniteNumber(value)) return value.toLocaleString('en-US')\n\t\treturn String(value)\n\t})\n}\n\n/**\n * Renders a {@link Premise} comparison as a display-neutral verb phrase.\n *\n * @param comparison - The comparison to describe\n * @returns A display-neutral phrase\n *\n * @example\n * ```ts\n * import { renderComparison } from '@orkestrel/qualifier'\n *\n * renderComparison('above') // 'is more than'\n * ```\n */\nexport function renderComparison(comparison: NonNullable<Premise['comparison']>): string {\n\tswitch (comparison) {\n\t\tcase 'equals':\n\t\t\treturn 'is'\n\t\tcase 'not':\n\t\t\treturn 'is not'\n\t\tcase 'above':\n\t\t\treturn 'is more than'\n\t\tcase 'below':\n\t\t\treturn 'is less than'\n\t\tcase 'from':\n\t\t\treturn 'is at least'\n\t\tcase 'to':\n\t\t\treturn 'is at most'\n\t\tcase 'any':\n\t\t\treturn 'is any of'\n\t\tcase 'none':\n\t\t\treturn 'is none of'\n\t\tcase 'between':\n\t\t\treturn 'is between'\n\t\tcase 'outside':\n\t\t\treturn 'is outside'\n\t}\n}\n\n/**\n * Renders a structured or scalar expected value display-neutrally.\n *\n * @remarks\n * An array renders as its elements, each `String`-coerced, joined with `', '`.\n * A reason `Bounds`-shaped record (the `between` / `outside` expected value)\n * renders its present sides joined with the word `and`; an absent side is\n * omitted. A finite number and everything else fall back to plain `String()` —\n * a finite number is not grouped here (grouping is {@link interpolateMessage}'s\n * message-token-only behavior).\n *\n * @param value - The value to render\n * @returns A display-neutral rendering\n *\n * @example\n * ```ts\n * import { renderValue } from '@orkestrel/qualifier'\n *\n * renderValue([18, 25, 40]) // '18, 25, 40'\n * renderValue({ minimum: 18, maximum: 65 }) // '18 and 65'\n * renderValue(42) // '42'\n * ```\n */\nexport function renderValue(value: unknown): string {\n\tif (isArray(value)) return value.map((entry) => String(entry)).join(', ')\n\tif (isBounds(value)) {\n\t\tconst sides: string[] = []\n\t\tif (value.minimum !== undefined) sides.push(String(value.minimum))\n\t\tif (value.maximum !== undefined) sides.push(String(value.maximum))\n\t\treturn sides.join(' and ')\n\t}\n\treturn String(value)\n}\n\n/**\n * Renders one {@link Premise} into a display-neutral sentence.\n *\n * @remarks\n * The checked form renders only when `field` and `comparison` are both\n * present, and `description` then goes unused; a premise missing either half\n * of the checked pair renders as described instead.\n *\n * @param entry - The premise to render\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A display-neutral sentence\n *\n * @example\n * ```ts\n * import { renderPremise } from '@orkestrel/qualifier'\n *\n * renderPremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })\n * // 'age is more than 18 → met'\n *\n * renderPremise({ description: 'Applicant is enrolled', met: true })\n * // 'Applicant is enrolled → met'\n * ```\n */\nexport function renderPremise(entry: Premise, labels?: Readonly<Record<string, string>>): string {\n\tconst status = entry.met === undefined ? 'unknown' : entry.met ? 'met' : 'not met'\n\tif (entry.field === undefined || entry.comparison === undefined) {\n\t\treturn `${entry.description ?? 'Premise'} → ${status}`\n\t}\n\tconst field = formatField(entry.field)\n\tconst label = labels?.[field] ?? entry.label ?? field\n\tconst expected = entry.expected === undefined ? '' : ` ${renderValue(entry.expected)}`\n\treturn `${label} ${renderComparison(entry.comparison)}${expected} → ${status}`\n}\n\n/**\n * Builds a {@link Premise} from an authored {@link Check} and its evaluated\n * `CheckResult`.\n *\n * @param check - The authored check\n * @param result - The evaluated check result carrying the resolved `actual` and `met`\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh premise\n *\n * @example\n * ```ts\n * import { checkToPremise } from '@orkestrel/qualifier'\n * import { createCheck } from '@orkestrel/reason'\n *\n * const authored = createCheck('age', 'above', 18)\n * checkToPremise(authored, { field: 'age', actual: 25, met: true })\n * // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }\n * ```\n */\nexport function checkToPremise(\n\tcheck: Check,\n\tresult: CheckResult,\n\tlabels?: Readonly<Record<string, string>>,\n): Premise {\n\tconst field = formatField(check.field)\n\treturn {\n\t\tfield: check.field,\n\t\t...(labels?.[field] === undefined ? {} : { label: labels[field] }),\n\t\tcomparison: check.operator,\n\t\texpected: check.value,\n\t\tactual: result.actual,\n\t\tmet: result.met,\n\t}\n}\n\n/**\n * Builds rich premises for one fired {@link Rule} by walking its premise atoms\n * and re-evaluating each against the working subject.\n *\n * @remarks\n * A reason rule result carries only booleans, so this is the qualifier's own\n * premise-audit projection: each authored premise expression is flattened to its\n * atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through\n * the injected `evaluator`. A membership check (`any` / `none`) over an empty\n * array value is content-free and is skipped.\n *\n * @param rule - The authored rule\n * @param working - The working subject to evaluate against\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh, ordered list of rich premises\n *\n * @example\n * ```ts\n * import { ruleToPremises } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const evaluator = createEvaluator()\n * const premises = ruleToPremises(gates.rules[0], { licensed: false }, evaluator)\n *\n * premises[0]?.met // true\n * ```\n */\nexport function ruleToPremises(\n\trule: Rule,\n\tworking: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): readonly Premise[] {\n\tconst output: Premise[] = []\n\tfor (const premise of rule.premises) {\n\t\tfor (const atom of extractAtoms(premise)) {\n\t\t\tconst { check } = atom\n\t\t\tif (\n\t\t\t\t(check.operator === 'any' || check.operator === 'none') &&\n\t\t\t\tisArray(check.value) &&\n\t\t\t\tcheck.value.length === 0\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutput.push(checkToPremise(check, evaluator.evaluate(check, working), labels))\n\t\t}\n\t}\n\treturn output\n}\n\n/**\n * Locates an authored {@link Rule} by id.\n *\n * @param definition - The logical definition to search\n * @param id - The rule id\n * @returns The matching rule, or `undefined`\n *\n * @example\n * ```ts\n * import { findRule } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n *\n * findRule(gates, 'licensed')?.id // 'licensed'\n * ```\n */\nexport function findRule(definition: LogicalDefinition, id: string): Rule | undefined {\n\treturn definition.rules.find((rule) => rule.id === id)\n}\n\n/**\n * Projects one reason result into the internal qualification namespace.\n *\n * @remarks\n * A quantitative result contributes its numeric `value`. A logical result\n * contributes a record of its `conclusion` plus every applied rule's asserted\n * conclusion fields (`extractConclusions`). A mismatched pairing contributes\n * `false`. The projection is pure — it never mutates the subject or the result.\n *\n * @param definition - The pass definition that produced the result\n * @param result - The evaluated reason result\n * @returns The pass's working projection\n *\n * @example\n * ```ts\n * import { reasonResultToProjection } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const result = {\n * reasoning: 'quantitative' as const,\n * value: 500_000,\n * groups: [],\n * count: 1,\n * success: true,\n * trace: [],\n * errors: [],\n * }\n * reasonResultToProjection(cap, result) // 500000\n * ```\n */\nexport function reasonResultToProjection(\n\tdefinition: QualificationPass,\n\tresult: ReasonResult,\n): QualificationProjection {\n\tif (definition.reasoning === 'quantitative' && result.reasoning === 'quantitative') {\n\t\treturn result.value\n\t}\n\tif (definition.reasoning !== 'logical' || result.reasoning !== 'logical') return false\n\tconst projection: Record<string, unknown> = { conclusion: result.conclusion }\n\tfor (const resolved of result.rules) {\n\t\tif (!resolved.applied) continue\n\t\tconst authored = definition.rules.find((rule) => rule.id === resolved.id)\n\t\tif (authored === undefined) continue\n\t\tconst conclusions = extractConclusions(authored.conclusion)\n\t\tfor (const [key, value] of Object.entries(conclusions)) projection[key] = value\n\t}\n\treturn projection\n}\n\n/**\n * Projects a quantitative result into a {@link Derivation} audit record.\n *\n * @param id - The pass id\n * @param result - The evaluated quantitative result\n * @returns A fresh derivation\n *\n * @example\n * ```ts\n * import { quantitativeResultToDerivation } from '@orkestrel/qualifier'\n *\n * const result = { reasoning: 'quantitative' as const, value: 500_000, groups: [], count: 1, success: true, trace: ['cap: 500000'], errors: [] }\n * quantitativeResultToDerivation('cap', result)\n * // { id: 'cap', value: 500000, success: true, trace: ['cap: 500000'], errors: [] }\n * ```\n */\nexport function quantitativeResultToDerivation(id: string, result: QuantitativeResult): Derivation {\n\treturn {\n\t\tid,\n\t\tvalue: result.value,\n\t\tsuccess: result.success,\n\t\ttrace: [...result.trace],\n\t\terrors: [...result.errors],\n\t}\n}\n\n/**\n * Wraps a {@link QualificationContext} under {@link QUALIFICATION_KEY}.\n *\n * @param context - The accumulated projection context\n * @returns A record carrying the context under the reserved key\n *\n * @example\n * ```ts\n * import { qualificationToRecord } from '@orkestrel/qualifier'\n *\n * qualificationToRecord({ cap: 500_000 }) // { qualification: { cap: 500000 } }\n * ```\n */\nexport function qualificationToRecord(\n\tcontext: QualificationContext,\n): Readonly<Record<string, QualificationContext>> {\n\treturn { [QUALIFICATION_KEY]: context }\n}\n\n/**\n * Merges one pass projection into the context, copy-on-write.\n *\n * @param context - The current projection context\n * @param id - The pass id to project under\n * @param projection - The pass's working projection\n * @returns A fresh context with the projection added\n *\n * @example\n * ```ts\n * import { mergeQualificationContext } from '@orkestrel/qualifier'\n *\n * mergeQualificationContext({ cap: 500_000 }, 'excess', 250_000)\n * // { cap: 500000, excess: 250000 }\n * ```\n */\nexport function mergeQualificationContext(\n\tcontext: QualificationContext,\n\tid: string,\n\tprojection: QualificationProjection,\n): QualificationContext {\n\treturn { ...context, [id]: projection }\n}\n\n/**\n * Joins a ruling, its logical rule result, the pass, the pre-projection subject,\n * and an evaluator into a {@link Finding}.\n *\n * @remarks\n * `applied` reflects whether the ruling's rule fired. Premises are re-evaluated\n * against the same subject snapshot the pass evaluated, so a conclusion the pass\n * derived can never satisfy its own reported premise. The message interpolates\n * against that snapshot.\n *\n * @param ruling - The authored ruling\n * @param pass - The pass the ruling's rule lives in\n * @param result - The pass's evaluated logical result\n * @param subject - The pre-projection subject snapshot the pass evaluated\n * @param evaluator - The shared reason check evaluator\n * @param labels - Optional field-to-label overrides, keyed by dot-joined field\n * @returns A fresh finding\n *\n * @example\n * ```ts\n * import { rulingToFinding } from '@orkestrel/qualifier'\n * import { createAtom, createEvaluator, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const ruling = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const }\n * const result = { reasoning: 'logical' as const, conclusion: true, rules: [{ id: 'licensed', applied: true, premises: [true] }], count: 1, success: true, trace: [], errors: [] }\n * const finding = rulingToFinding(ruling, gates, result, { licensed: false }, createEvaluator())\n *\n * finding.applied // true\n * ```\n */\nexport function rulingToFinding(\n\truling: Ruling,\n\tpass: QualificationPass,\n\tresult: LogicalResult,\n\tsubject: Subject,\n\tevaluator: EvaluatorInterface,\n\tlabels?: Readonly<Record<string, string>>,\n): Finding {\n\tconst entry = result.rules.find((resolved) => resolved.id === ruling.rule)\n\tconst applied = entry?.applied ?? false\n\tconst rule = pass.reasoning === 'logical' ? findRule(pass, ruling.rule) : undefined\n\tconst premises = rule === undefined ? [] : ruleToPremises(rule, subject, evaluator, labels)\n\treturn {\n\t\tid: ruling.id,\n\t\tpass: ruling.pass,\n\t\trule: ruling.rule,\n\t\teffect: ruling.effect,\n\t\t...(ruling.scope === undefined ? {} : { scope: ruling.scope }),\n\t\tapplied,\n\t\t...(ruling.message === undefined\n\t\t\t? {}\n\t\t\t: { message: interpolateMessage(ruling.message, subject) }),\n\t\tpremises,\n\t}\n}\n\n/**\n * Derives global eligibility from applied, unscoped findings.\n *\n * @remarks\n * Only findings that both applied and carry no `scope` shape global eligibility.\n * A failed pass contributes a synthetic `referral` (fail-closed), passed through\n * `failed`.\n *\n * @param findings - The resolved findings\n * @param failed - If `true`, adds a synthetic `referral` impact so an operational failure\n * fails closed; if `false`, derives eligibility from the findings alone. Default: `false`\n * @returns The most severe global eligibility\n *\n * @example\n * ```ts\n * import { deriveFindingEligibility } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'license', pass: 'gates', rule: 'licensed', effect: 'restriction' as const, applied: true, premises: [] }\n * deriveFindingEligibility([finding]) // 'ineligible'\n * ```\n */\nexport function deriveFindingEligibility(\n\tfindings: readonly Finding[],\n\tfailed = false,\n): Eligibility {\n\tconst eligibilities = findings\n\t\t.filter((finding) => finding.applied && finding.scope === undefined)\n\t\t.map((finding) => EFFECT_ELIGIBILITIES[finding.effect])\n\tif (failed) eligibilities.push('referral')\n\treturn combineEligibilities(eligibilities)\n}\n\n/**\n * Returns the most severe {@link Eligibility} in a list.\n *\n * @param eligibilities - The eligibilities to combine\n * @returns The most severe eligibility, or `'eligible'` for an empty list\n *\n * @example\n * ```ts\n * import { combineEligibilities } from '@orkestrel/qualifier'\n *\n * combineEligibilities(['eligible', 'referral']) // 'referral'\n * ```\n */\nexport function combineEligibilities(eligibilities: readonly Eligibility[]): Eligibility {\n\tfor (const eligibility of ELIGIBILITY_PRECEDENCE) {\n\t\tif (eligibilities.includes(eligibility)) return eligibility\n\t}\n\treturn 'eligible'\n}\n\n/**\n * Derives one eligibility per finding scope.\n *\n * @remarks\n * Only applied, scoped findings contribute. Each named scope combines its own\n * findings' impacts by severity. An unmentioned scope is absent (eligible by\n * default at the program layer).\n *\n * @param findings - The resolved findings\n * @returns A fresh record of scope to its combined eligibility\n *\n * @example\n * ```ts\n * import { deriveScopeEligibilities } from '@orkestrel/qualifier'\n *\n * const finding = { id: 'coastal', pass: 'wind', rule: 'coastal', effect: 'restriction' as const, scope: 'wind', applied: true, premises: [] }\n * deriveScopeEligibilities([finding]) // { wind: 'ineligible' }\n * ```\n */\nexport function deriveScopeEligibilities(\n\tfindings: readonly Finding[],\n): Readonly<Record<string, Eligibility>> {\n\tconst grouped: Record<string, Eligibility[]> = {}\n\tfor (const finding of findings) {\n\t\tif (!finding.applied || finding.scope === undefined) continue\n\t\tconst current = grouped[finding.scope] ?? []\n\t\tgrouped[finding.scope] = [...current, EFFECT_ELIGIBILITIES[finding.effect]]\n\t}\n\tconst scopes: Record<string, Eligibility> = {}\n\tfor (const [scope, eligibilities] of Object.entries(grouped)) {\n\t\tscopes[scope] = combineEligibilities(eligibilities)\n\t}\n\treturn scopes\n}\n\n/**\n * Describes each ruling whose pass or rule does not exist or whose pass is not\n * logical, and each pass id shadowing the reserved {@link QUALIFICATION_KEY}.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of reference-error messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, createRuling, describeMissingReferences } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates], {\n * rulings: [createRuling('license', 'gates', 'absent', 'restriction')],\n * })\n *\n * describeMissingReferences(definition)\n * // [\"Ruling 'license' references missing rule 'absent' in pass 'gates'\"]\n * ```\n */\nexport function describeMissingReferences(definition: QualificationDefinition): readonly string[] {\n\tconst errors: string[] = []\n\tconst passes = new Map(definition.passes.map((pass) => [pass.id, pass]))\n\tfor (const pass of definition.passes) {\n\t\tif (pass.id === QUALIFICATION_KEY) {\n\t\t\terrors.push(`Pass id must not equal reserved key '${QUALIFICATION_KEY}'`)\n\t\t}\n\t}\n\tfor (const ruling of definition.rulings ?? []) {\n\t\tconst pass = passes.get(ruling.pass)\n\t\tif (pass === undefined) {\n\t\t\terrors.push(`Ruling '${ruling.id}' references missing pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (pass.reasoning !== 'logical') {\n\t\t\terrors.push(`Ruling '${ruling.id}' references non-logical pass '${ruling.pass}'`)\n\t\t\tcontinue\n\t\t}\n\t\tif (findRule(pass, ruling.rule) === undefined) {\n\t\t\terrors.push(\n\t\t\t\t`Ruling '${ruling.id}' references missing rule '${ruling.rule}' in pass '${ruling.pass}'`,\n\t\t\t)\n\t\t}\n\t}\n\treturn errors\n}\n\n/**\n * Determines whether a subject already owns the reserved {@link QUALIFICATION_KEY}.\n *\n * @param subject - The subject to check\n * @returns True if the subject owns the reserved key; false otherwise\n *\n * @example\n * ```ts\n * import { hasReservedKey } from '@orkestrel/qualifier'\n *\n * hasReservedKey({ id: 's1', qualification: {} }) // true\n * ```\n */\nexport function hasReservedKey(subject: Subject): boolean {\n\treturn Object.hasOwn(subject, QUALIFICATION_KEY)\n}\n\n/**\n * Asserts a value is a valid qualification {@link Subject}, narrowing it in place.\n *\n * @param value - The candidate subject to validate\n * @throws {@link QualifierError} `'MISMATCH'` when the value is not a record, or\n * when it already carries the reserved `qualification` key\n *\n * @example\n * ```ts\n * import { assertSubject } from '@orkestrel/qualifier'\n *\n * assertSubject({ id: 's1' }) // narrows to Subject\n * ```\n */\nexport function assertSubject(value: unknown): asserts value is Subject {\n\tif (!isRecord(value)) {\n\t\tthrow new QualifierError('MISMATCH', 'Qualification subject must be a record')\n\t}\n\tif (hasReservedKey(value)) {\n\t\tthrow new QualifierError(\n\t\t\t'MISMATCH',\n\t\t\t`Qualification subject must not contain reserved key '${QUALIFICATION_KEY}'`,\n\t\t)\n\t}\n}\n\n/**\n * Maps an engine throw caught while running one pass to a typed {@link QualifierError}.\n *\n * @remarks\n * A reason `ReasonError('INVALID')` maps to `DEFINITION` (the engine rejected the\n * pass as an invalid definition); `ReasonError('DESTROYED')` maps to `DESTROYED`;\n * every other `ReasonError` code (for example `MISSING`) and every non-`ReasonError` throw\n * (including a `bail: true` reasoner rethrow) maps to `ENGINE`. The original throw\n * is preserved as `context.cause`.\n *\n * @param error - The value caught from the engine's `reason` call\n * @param pass - The id of the pass that was running\n * @returns A fresh, typed {@link QualifierError}\n *\n * @example\n * ```ts\n * import { mapEngineError } from '@orkestrel/qualifier'\n * import { ReasonError } from '@orkestrel/reason'\n *\n * mapEngineError(new ReasonError('MISSING', 'No reasoner registered'), 'cap').code // 'ENGINE'\n * ```\n */\nexport function mapEngineError(error: unknown, pass: string): QualifierError {\n\tif (isReasonError(error)) {\n\t\tif (error.code === 'INVALID') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DEFINITION',\n\t\t\t\t`Pass '${pass}' failed engine validation: ${error.message}`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\tif (error.code === 'DESTROYED') {\n\t\t\treturn new QualifierError(\n\t\t\t\t'DESTROYED',\n\t\t\t\t`Qualifier engine was destroyed during pass '${pass}'`,\n\t\t\t\t{ pass, cause: error },\n\t\t\t)\n\t\t}\n\t\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${error.message}`, {\n\t\t\tpass,\n\t\t\tcause: error,\n\t\t})\n\t}\n\tconst message = isError(error) ? error.message : String(error)\n\treturn new QualifierError('ENGINE', `Pass '${pass}' engine failure: ${message}`, {\n\t\tpass,\n\t\tcause: error,\n\t})\n}\n\n/**\n * Describes each logical pass carrying no rulings.\n *\n * @remarks\n * A logical pass with no matching ruling can never contribute a finding, so its\n * rules only ever influence later passes' reads — this is very likely an authoring\n * oversight.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeEmptyLogicalPasses } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Gates', [\n * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [gates])\n *\n * describeEmptyLogicalPasses(definition) // [\"Logical pass 'gates' has no rulings\"]\n * ```\n */\nexport function describeEmptyLogicalPasses(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tfor (const pass of definition.passes) {\n\t\tif (pass.reasoning !== 'logical') continue\n\t\tconst hasRuling = (definition.rulings ?? []).some((ruling) => ruling.pass === pass.id)\n\t\tif (!hasRuling) warnings.push(`Logical pass '${pass.id}' has no rulings`)\n\t}\n\treturn warnings\n}\n\n/**\n * Describes each quantitative pass never read by a later pass.\n *\n * @remarks\n * A quantitative pass's projected value lives under `qualification.<id>` — if no\n * later logical premise or quantitative factor field reads that key, the\n * derivation is dead weight. Reads are collected per later pass: a logical pass's\n * premise atoms (conclusion atoms are writes, not reads), and a quantitative\n * pass's field/lookup/range factor sources plus factor checks.\n *\n * @param definition - The qualification definition to check\n * @returns A fresh list of warning messages\n *\n * @example\n * ```ts\n * import { createQualificationDefinition, describeUnreadDerivations } from '@orkestrel/qualifier'\n * import { createFactorGroup, createQuantitativeDefinition, createStaticFactor } from '@orkestrel/reason'\n *\n * const cap = createQuantitativeDefinition('cap', 'TIV cap', [\n * createFactorGroup('limit', 'sum', [createStaticFactor('base', 500_000)]),\n * ])\n * const definition = createQualificationDefinition('standard', 'Standard', [cap])\n *\n * describeUnreadDerivations(definition) // [\"Quantitative pass 'cap' is never read by a later pass\"]\n * ```\n */\nexport function describeUnreadDerivations(definition: QualificationDefinition): readonly string[] {\n\tconst warnings: string[] = []\n\tdefinition.passes.forEach((pass, index) => {\n\t\tif (pass.reasoning !== 'quantitative') return\n\t\tconst key = formatField(['qualification', pass.id])\n\t\tconst reads = new Set<string>()\n\t\tfor (const later of definition.passes.slice(index + 1)) {\n\t\t\tif (later.reasoning === 'logical') {\n\t\t\t\tfor (const rule of later.rules) {\n\t\t\t\t\tfor (const premise of rule.premises) {\n\t\t\t\t\t\tfor (const atom of extractAtoms(premise)) reads.add(formatField(atom.check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (later.reasoning === 'quantitative') {\n\t\t\t\tfor (const group of later.groups) {\n\t\t\t\t\tfor (const factor of group.factors) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tfactor.source.origin === 'field' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'lookup' ||\n\t\t\t\t\t\t\tfactor.source.origin === 'range'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treads.add(formatField(factor.source.field))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const check of factor.checks ?? []) reads.add(formatField(check.field))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!reads.has(key)) {\n\t\t\twarnings.push(`Quantitative pass '${pass.id}' is never read by a later pass`)\n\t\t}\n\t})\n\treturn warnings\n}\n","import type {\n\tDerivation,\n\tFinding,\n\tQualificationContext,\n\tQualificationDefinition,\n\tQualificationPass,\n\tQualificationResult,\n\tQualifierEventMap,\n\tQualifierInterface,\n\tQualifierOptions,\n} from './types.js'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tEvaluatorInterface,\n\tReasonInterface,\n\tReasonResult,\n\tReasonValidationResult,\n\tSubject,\n} from '@orkestrel/reason'\nimport { DEFAULT_QUALIFIER_VALIDATE } from './constants.js'\nimport { QualifierError } from './errors.js'\nimport {\n\tassertSubject,\n\tdescribeEmptyLogicalPasses,\n\tdescribeMissingReferences,\n\tdescribeUnreadDerivations,\n\tderiveFindingEligibility,\n\tderiveScopeEligibilities,\n\tmapEngineError,\n\tmergeQualificationContext,\n\tqualificationToRecord,\n\tquantitativeResultToDerivation,\n\treasonResultToProjection,\n\trulingToFinding,\n} from './helpers.js'\nimport { isQualificationPass, isRuling } from './validators.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport {\n\tcreateEvaluator,\n\tcreateLogicalReasoner,\n\tcreateQuantitativeReasoner,\n\tcreateReason,\n\tfindDuplicates,\n} from '@orkestrel/reason'\n\n/**\n * Runs ordered passes over one reason engine and returns eligibility.\n *\n * @remarks\n * The engine is owned when self-created (destroyed on `destroy()`) and borrowed\n * when injected (never destroyed). Semantic validation runs before the first pass\n * whenever the `validate` option is on. `qualify` builds its working subject through\n * copy-on-write overlays only — the caller's `subject` is never mutated, and the\n * qualifier's internal projection is discarded after each call.\n */\nexport class Qualifier implements QualifierInterface {\n\treadonly #emitter: Emitter<QualifierEventMap>\n\treadonly #engine: ReasonInterface\n\treadonly #evaluator: EvaluatorInterface\n\treadonly #owned: boolean\n\treadonly #validate: boolean\n\treadonly #labels: Readonly<Record<string, string>> | undefined\n\t#destroyed = false\n\n\tconstructor(options?: QualifierOptions) {\n\t\tthis.#emitter = new Emitter({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\tthis.#evaluator = createEvaluator()\n\t\tthis.#owned = options?.engine === undefined\n\t\tthis.#engine =\n\t\t\toptions?.engine ??\n\t\t\tcreateReason({\n\t\t\t\treasoners: [\n\t\t\t\t\tcreateQuantitativeReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t\tcreateLogicalReasoner({ evaluator: this.#evaluator }),\n\t\t\t\t],\n\t\t\t\tbail: false,\n\t\t\t})\n\t\tthis.#validate = options?.validate ?? DEFAULT_QUALIFIER_VALIDATE\n\t\tthis.#labels = options?.labels\n\t}\n\n\t/**\n\t * Holds the typed observation surface carrying `derive`, `finding`, `qualify`, and\n\t * `destroy`.\n\t *\n\t * @returns The emitter this qualifier owns\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.emitter.on('qualify', (result) => console.log(result.eligibility))\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tget emitter(): EmitterInterface<QualifierEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/**\n\t * Qualifies one subject against one authored definition.\n\t *\n\t * @remarks\n\t * Semantic validation runs first when the `validate` option is on, so an invalid\n\t * definition throws before any pass runs. Passes run in authored order over a\n\t * copy-on-write working subject, and the caller's `subject` is never mutated. An\n\t * unscoped restriction stops the remaining passes because no later finding can be\n\t * more severe.\n\t *\n\t * @param subject - The record to qualify\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh, frozen qualification result\n\t * @throws {@link QualifierError} `'DEFINITION'` when validation is enabled and the\n\t * definition fails semantic validation, or when the engine rejects a pass as an invalid\n\t * definition; `'MISMATCH'` when the subject is not a record or already carries the\n\t * reserved `qualification` key; `'DESTROYED'` after `destroy`, or when the engine\n\t * reports itself destroyed mid-pass; `'ENGINE'` for every other engine throw\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, createRuling, Qualifier } from '@orkestrel/qualifier'\n\t * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n\t *\n\t * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n\t * createRule('licensed', [createAtom('licensed', 'equals', false)], createAtom('blocked', 'equals', true)),\n\t * ])\n\t * const definition = createQualificationDefinition('standard', 'Standard eligibility', [gates], {\n\t * rulings: [createRuling('license', 'gates', 'licensed', 'restriction')],\n\t * })\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.qualify({ id: 'risk-1', licensed: false }, definition).eligibility // 'ineligible'\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tqualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tthis.#alive()\n\t\tif (this.#validate) {\n\t\t\tconst validation = this.validate(definition)\n\t\t\tif (!validation.valid) {\n\t\t\t\tthrow new QualifierError(\n\t\t\t\t\t'DEFINITION',\n\t\t\t\t\t`Qualification definition is invalid: ${validation.errors.join(', ')}`,\n\t\t\t\t\t{ definition: definition.id },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn this.#qualify(subject, definition)\n\t}\n\n\t/**\n\t * Validates one authored definition semantically, without running it.\n\t *\n\t * @remarks\n\t * Structural shape is `isQualificationDefinition`'s job. This checks the meaning:\n\t * non-empty id and name, valid and uniquely identified passes and rulings, every\n\t * ruling reference resolving to a logical pass and one of its rules, and no pass id\n\t * shadowing `QUALIFICATION_KEY`. An empty definition, a logical pass carrying no\n\t * ruling, and a derivation no later pass reads are warnings rather than errors.\n\t *\n\t * @param definition - The authored qualification definition\n\t * @returns A fresh validation result carrying `valid`, `errors`, and `warnings`\n\t * @throws {@link QualifierError} `'DESTROYED'` after `destroy`\n\t *\n\t * @example\n\t * ```ts\n\t * import { createQualificationDefinition, Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.validate(createQualificationDefinition('empty', 'Empty', []))\n\t * // { valid: true, errors: [], warnings: ['Definition has no passes'] }\n\t * qualifier.destroy()\n\t * ```\n\t */\n\tvalidate(definition: QualificationDefinition): ReasonValidationResult {\n\t\tthis.#alive()\n\t\tconst errors: string[] = []\n\t\tconst warnings: string[] = []\n\t\tif (definition.id.length === 0) errors.push('Definition id must not be empty')\n\t\tif (definition.name.length === 0) errors.push('Definition name must not be empty')\n\t\tdefinition.passes.forEach((pass, index) => {\n\t\t\tif (!isQualificationPass(pass)) {\n\t\t\t\terrors.push(`Pass at index ${index} is not a valid qualification pass`)\n\t\t\t}\n\t\t})\n\t\t;(definition.rulings ?? []).forEach((ruling, index) => {\n\t\t\tif (!isRuling(ruling)) errors.push(`Ruling at index ${index} is not a valid ruling`)\n\t\t})\n\t\tfor (const id of findDuplicates(definition.passes)) errors.push(`Duplicate pass id '${id}'`)\n\t\tfor (const id of findDuplicates(definition.rulings ?? [])) {\n\t\t\terrors.push(`Duplicate ruling id '${id}'`)\n\t\t}\n\t\tfor (const reference of describeMissingReferences(definition)) errors.push(reference)\n\t\tif (definition.passes.length === 0) warnings.push('Definition has no passes')\n\t\tfor (const warning of describeEmptyLogicalPasses(definition)) warnings.push(warning)\n\t\tfor (const warning of describeUnreadDerivations(definition)) warnings.push(warning)\n\t\treturn { valid: errors.length === 0, errors, warnings }\n\t}\n\n\t/**\n\t * Destroys this qualifier, idempotently.\n\t *\n\t * @remarks\n\t * An engine this qualifier created is destroyed; an injected engine stays\n\t * caller-owned and is left alone. The `destroy` event fires before the emitter\n\t * itself is destroyed, and a later call returns without re-emitting.\n\t *\n\t * @example\n\t * ```ts\n\t * import { Qualifier } from '@orkestrel/qualifier'\n\t *\n\t * const qualifier = new Qualifier()\n\t * qualifier.destroy()\n\t * qualifier.destroy() // idempotent\n\t * ```\n\t */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tif (this.#owned) this.#engine.destroy()\n\t\tthis.#destroyed = true\n\t\tthis.#emitter.emit('destroy')\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#qualify(subject: Subject, definition: QualificationDefinition): QualificationResult {\n\t\tassertSubject(subject)\n\t\tconst steps: Array<{\n\t\t\treadonly context: QualificationContext\n\t\t\treadonly subject: Subject\n\t\t\treadonly success: boolean\n\t\t}> = [{ context: {}, subject: { ...subject }, success: true }]\n\t\tconst findings: Finding[] = []\n\t\tconst derivations: Derivation[] = []\n\t\tconst trace: string[] = []\n\t\tconst errors: string[] = []\n\n\t\tfor (const pass of definition.passes) {\n\t\t\tthis.#alive()\n\t\t\tconst previous = steps.at(-1)\n\t\t\tif (previous === undefined) throw new Error('Qualification has no initial step')\n\t\t\tconst evaluated = previous.subject\n\t\t\tconst result = this.#reason(evaluated, pass)\n\n\t\t\ttrace.push(...result.trace.map((entry) => `${pass.id}: ${entry}`))\n\t\t\terrors.push(...result.errors.map((entry) => `${pass.id}: ${entry}`))\n\n\t\t\tif (result.reasoning === 'quantitative') {\n\t\t\t\tconst derivation = quantitativeResultToDerivation(pass.id, result)\n\t\t\t\tderivations.push(derivation)\n\t\t\t\tthis.#emitter.emit('derive', derivation)\n\t\t\t}\n\n\t\t\tif (result.reasoning === 'logical') {\n\t\t\t\tconst rulings = (definition.rulings ?? []).filter((ruling) => ruling.pass === pass.id)\n\t\t\t\tfor (const ruling of rulings) {\n\t\t\t\t\tconst finding = rulingToFinding(\n\t\t\t\t\t\truling,\n\t\t\t\t\t\tpass,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tevaluated,\n\t\t\t\t\t\tthis.#evaluator,\n\t\t\t\t\t\tthis.#labels,\n\t\t\t\t\t)\n\t\t\t\t\tfindings.push(finding)\n\t\t\t\t\tthis.#emitter.emit('finding', finding)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst projection = reasonResultToProjection(pass, result)\n\t\t\tconst context = mergeQualificationContext(previous.context, pass.id, projection)\n\t\t\tsteps.push({\n\t\t\t\tcontext,\n\t\t\t\tsubject: { ...subject, ...qualificationToRecord(context) },\n\t\t\t\tsuccess: result.success,\n\t\t\t})\n\n\t\t\tif (!result.success) break\n\t\t\tif (deriveFindingEligibility(findings) === 'ineligible') break\n\t\t}\n\n\t\tconst success = steps.every((step) => step.success) && errors.length === 0\n\t\tconst scopes = deriveScopeEligibilities(findings)\n\t\tconst qualification: QualificationResult = Object.freeze({\n\t\t\tid: definition.id,\n\t\t\tname: definition.name,\n\t\t\teligibility: deriveFindingEligibility(findings, !success),\n\t\t\tscopes: Object.freeze(scopes),\n\t\t\tfindings: Object.freeze(findings),\n\t\t\tderivations: Object.freeze(derivations),\n\t\t\tsuccess,\n\t\t\ttrace: Object.freeze(trace),\n\t\t\terrors: Object.freeze(errors),\n\t\t})\n\t\tthis.#emitter.emit('qualify', qualification)\n\t\treturn qualification\n\t}\n\n\t#reason(subject: Subject, pass: QualificationPass): ReasonResult {\n\t\ttry {\n\t\t\treturn this.#engine.reason(subject, pass)\n\t\t} catch (error) {\n\t\t\tthrow mapEngineError(error, pass.id)\n\t\t}\n\t}\n\n\t#alive(): void {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new QualifierError('DESTROYED', 'Qualifier has been destroyed')\n\t\t}\n\t}\n}\n","import type {\n\tQualificationDefinition,\n\tQualificationEffect,\n\tQualificationInput,\n\tQualificationPass,\n\tQualifierInterface,\n\tQualifierOptions,\n\tRuling,\n\tRulingInput,\n} from './types.js'\nimport { Qualifier } from './Qualifier.js'\n\n/**\n * Creates one {@link QualifierInterface} over a reason engine.\n *\n * @remarks\n * A standalone qualifier creates and owns one shared quantitative-plus-logical\n * reason engine, destroying it on `destroy()`. An injected `options.engine`\n * remains caller-owned and is never destroyed. `validate` runs semantic\n * validation before every qualification. Default: `true`. `labels` overrides the\n * display name of a premise field, keyed by the dot-joined field path\n * `formatField` produces, and takes precedence over a premise's own `label`.\n *\n * @param options - Optional injected engine, validation, labels, and emitter hooks\n * @returns A {@link QualifierInterface}\n *\n * @example Create a qualifier\n * ```ts\n * import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'\n * import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'\n *\n * const gates = createLogicalDefinition('gates', 'Eligibility gates', [\n * \tcreateRule(\n * \t\t'licensed',\n * \t\t[createAtom('licensed', 'equals', false)],\n * \t\tcreateAtom('blocked', 'equals', true),\n * \t),\n * ])\n *\n * const bare = createRuling('license', 'gates', 'licensed', 'restriction')\n * const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {\n * \tmessage: 'A license is required',\n * })\n *\n * 'message' in bare // false — an absent optional key is omitted, never written as undefined\n * messaged.message // 'A license is required'\n *\n * const passes = [gates]\n * const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {\n * \trulings: [messaged],\n * })\n *\n * 'description' in definition // false\n * definition.passes === passes // false — the factory copies what it is handed\n *\n * const qualifier = createQualifier()\n * qualifier.qualify({ id: 'risk-1', licensed: false }, definition)\n * qualifier.destroy()\n * ```\n */\nexport function createQualifier(options?: QualifierOptions): QualifierInterface {\n\treturn new Qualifier(options)\n}\n\n/**\n * Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.\n *\n * @remarks\n * `passes` and `rulings` arrays are copied, and record `metadata` is\n * shallow-copied so nested values are not deep-cloned.\n *\n * @param id - The definition id\n * @param name - The display name\n * @param passes - The ordered qualification passes\n * @param input - Optional description, rulings, and metadata\n * @returns A fresh qualification definition\n *\n * @example\n * ```ts\n * import { createQualificationDefinition } from '@orkestrel/qualifier'\n *\n * createQualificationDefinition('standard', 'Standard', [gates], { rulings: [ruling] })\n * ```\n */\nexport function createQualificationDefinition(\n\tid: string,\n\tname: string,\n\tpasses: readonly QualificationPass[],\n\tinput?: QualificationInput,\n): QualificationDefinition {\n\treturn {\n\t\tid,\n\t\tname,\n\t\tpasses: [...passes],\n\t\t...(input?.description === undefined ? {} : { description: input.description }),\n\t\t...(input?.rulings === undefined ? {} : { rulings: [...input.rulings] }),\n\t\t...(input?.metadata === undefined ? {} : { metadata: { ...input.metadata } }),\n\t}\n}\n\n/**\n * Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.\n *\n * @param id - The ruling id\n * @param pass - The logical pass id the rule lives in\n * @param rule - The rule id whose firing this ruling reacts to\n * @param effect - The eligibility impact when the rule fires\n * @param input - Optional selection scope and message template\n * @returns A fresh ruling\n *\n * @example\n * ```ts\n * import { createRuling } from '@orkestrel/qualifier'\n *\n * createRuling('license', 'gates', 'licensed', 'restriction', { message: 'A license is required' })\n * ```\n */\nexport function createRuling(\n\tid: string,\n\tpass: string,\n\trule: string,\n\teffect: QualificationEffect,\n\tinput?: RulingInput,\n): Ruling {\n\treturn {\n\t\tid,\n\t\tpass,\n\t\trule,\n\t\teffect,\n\t\t...(input?.scope === undefined ? {} : { scope: input.scope }),\n\t\t...(input?.message === undefined ? {} : { message: input.message }),\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/qualifier",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "A typed eligibility engine: ordered quantitative and logical passes qualify a subject into program-level and scoped eligibility with evidence-rich findings. Part of the @orkestrel line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eligibility",
|
|
@@ -65,26 +65,26 @@
|
|
|
65
65
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release",
|
|
66
66
|
"test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
|
|
67
67
|
"test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
|
|
68
|
-
"test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
|
|
68
|
+
"test:bench": "vitest bench --config vite.config.ts --no-cache --project \"probe (bench)\" --mode benchmark",
|
|
69
69
|
"test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@orkestrel/contract": "^0.0.
|
|
73
|
-
"@orkestrel/emitter": "^0.0.
|
|
74
|
-
"@orkestrel/reason": "^0.0.
|
|
72
|
+
"@orkestrel/contract": "^0.0.20",
|
|
73
|
+
"@orkestrel/emitter": "^0.0.13",
|
|
74
|
+
"@orkestrel/reason": "^0.0.15"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@microsoft/api-extractor": "^7.
|
|
78
|
-
"@orkestrel/guide": "^0.0.
|
|
79
|
-
"@orkestrel/probe": "^0.0.
|
|
80
|
-
"@orkestrel/scaffold": "^0.0.
|
|
81
|
-
"@orkestrel/test": "^0.0.
|
|
82
|
-
"@types/node": "^26.6.
|
|
83
|
-
"oxfmt": "^0.
|
|
84
|
-
"oxlint": "^1.
|
|
85
|
-
"typescript": "^
|
|
86
|
-
"vite": "^8.3.
|
|
87
|
-
"vitest": "^
|
|
77
|
+
"@microsoft/api-extractor": "^7.60.0",
|
|
78
|
+
"@orkestrel/guide": "^0.0.26",
|
|
79
|
+
"@orkestrel/probe": "^0.0.26",
|
|
80
|
+
"@orkestrel/scaffold": "^0.0.113",
|
|
81
|
+
"@orkestrel/test": "^0.0.31",
|
|
82
|
+
"@types/node": "^26.6.6",
|
|
83
|
+
"oxfmt": "^0.75.0",
|
|
84
|
+
"oxlint": "^1.90.0",
|
|
85
|
+
"typescript": "^7.0.2",
|
|
86
|
+
"vite": "^8.3.5",
|
|
87
|
+
"vitest": "^5.0.5"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=22.12.0"
|