@arkyc/core 2.1.1 → 2.3.0
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/decision.mjs +1 -1
- package/dist/decision.mjs.map +1 -1
- package/package.json +2 -2
package/dist/decision.mjs
CHANGED
|
@@ -30,7 +30,7 @@ var DecisionEngine = class {
|
|
|
30
30
|
});
|
|
31
31
|
if (input.document.expired) return verdict("rejected", "DOCUMENT_EXPIRED");
|
|
32
32
|
if (!input.liveness.passed) return verdict("rejected", "LIVENESS_FAILED");
|
|
33
|
-
if (!input.faceMatch.passed) return verdict("rejected", "FACE_MATCH_FAILED");
|
|
33
|
+
if (typeof t.faceMatchRejectThreshold === "number" ? input.faceMatch.similarityScore < t.faceMatchRejectThreshold : !input.faceMatch.passed) return verdict("rejected", "FACE_MATCH_FAILED");
|
|
34
34
|
if (!input.address.passed && input.address.onFail === "reject") return verdict("rejected", "ADDRESS_VERIFICATION_FAILED");
|
|
35
35
|
const signals = input.signals ?? [];
|
|
36
36
|
const rejectSignal = RiskSignals.reasonFor(signals, "reject");
|
package/dist/decision.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.mjs","names":[],"sources":["../src/decision.ts"],"sourcesContent":["import type { DecisionReason, RiskSignal, VerificationDecision, VerificationThresholds } from '@arkyc/types'\n\nimport { Risk } from './risk'\nimport { RiskSignals } from './signals'\nimport { Thresholds } from './thresholds'\n\n/** Document signals fed to the decision engine. */\nexport interface DecisionDocumentInput {\n /** Document image quality in [0, 1]. */\n qualityScore: number\n /** OCR extraction confidence in [0, 1]. */\n ocrConfidence: number\n /** Whether the document is past its expiry date. */\n expired: boolean\n}\n\n/** Liveness signals fed to the decision engine. */\nexport interface DecisionLivenessInput {\n passed: boolean\n /** Liveness confidence in [0, 1]. */\n score: number\n /** Whether more than one face was detected in the selfie/liveness frame. */\n multipleFaces?: boolean\n}\n\n/** Face-match signals fed to the decision engine. */\nexport interface DecisionFaceMatchInput {\n passed: boolean\n /** Similarity between document portrait and selfie in [0, 1]. */\n similarityScore: number\n}\n\n/** Address-verification signals fed to the decision engine. */\nexport interface DecisionAddressInput {\n passed: boolean\n /** Overall address-verification confidence in [0, 1]. */\n score: number\n /**\n * What a failed result does: flag for `review` (default) or `reject`, driven\n * by the workflow's address config. A low-confidence (but not failed) result\n * is always review, never a reject.\n */\n onFail?: 'review' | 'reject'\n /**\n * Workflow-configured minimum confidence to auto-verify the address. A passed\n * address scoring below this routes to manual review. Falls back to the\n * project `addressThreshold` when omitted.\n */\n threshold?: number\n}\n\n/** The complete set of signals the decision engine evaluates. */\nexport interface DecisionInput {\n document: DecisionDocumentInput\n liveness: DecisionLivenessInput\n faceMatch: DecisionFaceMatchInput\n address: DecisionAddressInput\n /** Fraud and compliance signals, with their policy-resolved severity. */\n signals?: RiskSignal[]\n}\n\n/** The decision engine's verdict for a session. */\nexport interface DecisionResult {\n decision: VerificationDecision\n reason: DecisionReason\n /** Aggregate risk score in [0, 1] (higher = riskier). */\n riskScore: number\n}\n\n/** Deterministic verification decision engine. */\nexport class DecisionEngine {\n /**\n * Decide a verification outcome from the gathered signals.\n *\n * Hard failures reject outright; borderline/low-confidence signals route to\n * manual review; everything clearing its threshold is auto-approved. Checks are\n * evaluated in a fixed priority order so the result is deterministic.\n *\n * Reject precedence: expired → liveness failed → face-match failed\n * → address failed → reject signals\n * Review precedence: multiple faces → low quality → low OCR\n * → low liveness → low face-match → address → review signals\n *\n * @param input\n * @param thresholds\n * @returns\n */\n static decide(input: DecisionInput, thresholds?: Partial<VerificationThresholds>): DecisionResult {\n const t = Thresholds.resolve(thresholds)\n const riskScore = Risk.score(input)\n const verdict = (decision: VerificationDecision, reason: DecisionReason): DecisionResult => ({\n decision,\n reason,\n riskScore,\n })\n\n // --- Hard rejections (highest priority) ---\n if (input.document.expired) {\n return verdict('rejected', 'DOCUMENT_EXPIRED')\n }\n if (!input.liveness.passed) {\n return verdict('rejected', 'LIVENESS_FAILED')\n }\n
|
|
1
|
+
{"version":3,"file":"decision.mjs","names":[],"sources":["../src/decision.ts"],"sourcesContent":["import type { DecisionReason, RiskSignal, VerificationDecision, VerificationThresholds } from '@arkyc/types'\n\nimport { Risk } from './risk'\nimport { RiskSignals } from './signals'\nimport { Thresholds } from './thresholds'\n\n/** Document signals fed to the decision engine. */\nexport interface DecisionDocumentInput {\n /** Document image quality in [0, 1]. */\n qualityScore: number\n /** OCR extraction confidence in [0, 1]. */\n ocrConfidence: number\n /** Whether the document is past its expiry date. */\n expired: boolean\n}\n\n/** Liveness signals fed to the decision engine. */\nexport interface DecisionLivenessInput {\n passed: boolean\n /** Liveness confidence in [0, 1]. */\n score: number\n /** Whether more than one face was detected in the selfie/liveness frame. */\n multipleFaces?: boolean\n}\n\n/** Face-match signals fed to the decision engine. */\nexport interface DecisionFaceMatchInput {\n passed: boolean\n /** Similarity between document portrait and selfie in [0, 1]. */\n similarityScore: number\n}\n\n/** Address-verification signals fed to the decision engine. */\nexport interface DecisionAddressInput {\n passed: boolean\n /** Overall address-verification confidence in [0, 1]. */\n score: number\n /**\n * What a failed result does: flag for `review` (default) or `reject`, driven\n * by the workflow's address config. A low-confidence (but not failed) result\n * is always review, never a reject.\n */\n onFail?: 'review' | 'reject'\n /**\n * Workflow-configured minimum confidence to auto-verify the address. A passed\n * address scoring below this routes to manual review. Falls back to the\n * project `addressThreshold` when omitted.\n */\n threshold?: number\n}\n\n/** The complete set of signals the decision engine evaluates. */\nexport interface DecisionInput {\n document: DecisionDocumentInput\n liveness: DecisionLivenessInput\n faceMatch: DecisionFaceMatchInput\n address: DecisionAddressInput\n /** Fraud and compliance signals, with their policy-resolved severity. */\n signals?: RiskSignal[]\n}\n\n/** The decision engine's verdict for a session. */\nexport interface DecisionResult {\n decision: VerificationDecision\n reason: DecisionReason\n /** Aggregate risk score in [0, 1] (higher = riskier). */\n riskScore: number\n}\n\n/** Deterministic verification decision engine. */\nexport class DecisionEngine {\n /**\n * Decide a verification outcome from the gathered signals.\n *\n * Hard failures reject outright; borderline/low-confidence signals route to\n * manual review; everything clearing its threshold is auto-approved. Checks are\n * evaluated in a fixed priority order so the result is deterministic.\n *\n * Reject precedence: expired → liveness failed → face-match failed\n * → address failed → reject signals\n * Review precedence: multiple faces → low quality → low OCR\n * → low liveness → low face-match → address → review signals\n *\n * @param input\n * @param thresholds\n * @returns\n */\n static decide(input: DecisionInput, thresholds?: Partial<VerificationThresholds>): DecisionResult {\n const t = Thresholds.resolve(thresholds)\n const riskScore = Risk.score(input)\n const verdict = (decision: VerificationDecision, reason: DecisionReason): DecisionResult => ({\n decision,\n reason,\n riskScore,\n })\n\n // --- Hard rejections (highest priority) ---\n if (input.document.expired) {\n return verdict('rejected', 'DOCUMENT_EXPIRED')\n }\n if (!input.liveness.passed) {\n return verdict('rejected', 'LIVENESS_FAILED')\n }\n\n const faceMatchRejected =\n typeof t.faceMatchRejectThreshold === 'number'\n ? input.faceMatch.similarityScore < t.faceMatchRejectThreshold\n : !input.faceMatch.passed\n\n if (faceMatchRejected) {\n return verdict('rejected', 'FACE_MATCH_FAILED')\n }\n // Address only rejects when the workflow opts in (`on_fail: 'reject'`);\n // otherwise a failed address falls through to manual review below.\n if (!input.address.passed && input.address.onFail === 'reject') {\n return verdict('rejected', 'ADDRESS_VERIFICATION_FAILED')\n }\n\n const signals = input.signals ?? []\n const rejectSignal = RiskSignals.reasonFor(signals, 'reject')\n if (rejectSignal) {\n return verdict('rejected', rejectSignal)\n }\n\n // --- Manual review (borderline / ambiguous signals) ---\n if (input.liveness.multipleFaces === true) {\n return verdict('requires_review', 'MULTIPLE_FACES_DETECTED')\n }\n if (input.document.qualityScore < t.documentQualityThreshold) {\n return verdict('requires_review', 'LOW_DOCUMENT_QUALITY')\n }\n if (input.document.ocrConfidence < t.ocrConfidenceThreshold) {\n return verdict('requires_review', 'OCR_LOW_CONFIDENCE')\n }\n if (input.liveness.score < t.livenessThreshold) {\n return verdict('requires_review', 'LIVENESS_LOW_CONFIDENCE')\n }\n if (input.faceMatch.similarityScore < t.faceMatchThreshold) {\n return verdict('requires_review', 'FACE_MATCH_LOW_CONFIDENCE')\n }\n // A failed address that didn't reject (on_fail: 'review') lands here.\n if (!input.address.passed) {\n return verdict('requires_review', 'ADDRESS_VERIFICATION_FAILED')\n }\n // Auto-verify gate: the workflow can set its own minimum address confidence;\n // a passed address below it is held for manual review (never rejected here).\n if (input.address.score < (input.address.threshold ?? t.addressThreshold)) {\n return verdict('requires_review', 'ADDRESS_LOW_CONFIDENCE')\n }\n\n const reviewSignal = RiskSignals.reasonFor(signals, 'review')\n if (reviewSignal) {\n return verdict('requires_review', reviewSignal)\n }\n\n // --- All checks cleared ---\n return verdict('approved', 'AUTO_APPROVED')\n }\n}\n"],"mappings":";;;;;AAsEA,IAAa,iBAAb,MAA4B;;;;;;;;;;;;;;;;;CAiB1B,OAAO,OAAO,OAAsB,YAA8D;EAChG,MAAM,IAAI,WAAW,QAAQ,UAAU;EACvC,MAAM,YAAY,KAAK,MAAM,KAAK;EAClC,MAAM,WAAW,UAAgC,YAA4C;GAC3F;GACA;GACA;EACF;EAGA,IAAI,MAAM,SAAS,SACjB,OAAO,QAAQ,YAAY,kBAAkB;EAE/C,IAAI,CAAC,MAAM,SAAS,QAClB,OAAO,QAAQ,YAAY,iBAAiB;EAQ9C,IAJE,OAAO,EAAE,6BAA6B,WAClC,MAAM,UAAU,kBAAkB,EAAE,2BACpC,CAAC,MAAM,UAAU,QAGrB,OAAO,QAAQ,YAAY,mBAAmB;EAIhD,IAAI,CAAC,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,UACpD,OAAO,QAAQ,YAAY,6BAA6B;EAG1D,MAAM,UAAU,MAAM,WAAW,CAAC;EAClC,MAAM,eAAe,YAAY,UAAU,SAAS,QAAQ;EAC5D,IAAI,cACF,OAAO,QAAQ,YAAY,YAAY;EAIzC,IAAI,MAAM,SAAS,kBAAkB,MACnC,OAAO,QAAQ,mBAAmB,yBAAyB;EAE7D,IAAI,MAAM,SAAS,eAAe,EAAE,0BAClC,OAAO,QAAQ,mBAAmB,sBAAsB;EAE1D,IAAI,MAAM,SAAS,gBAAgB,EAAE,wBACnC,OAAO,QAAQ,mBAAmB,oBAAoB;EAExD,IAAI,MAAM,SAAS,QAAQ,EAAE,mBAC3B,OAAO,QAAQ,mBAAmB,yBAAyB;EAE7D,IAAI,MAAM,UAAU,kBAAkB,EAAE,oBACtC,OAAO,QAAQ,mBAAmB,2BAA2B;EAG/D,IAAI,CAAC,MAAM,QAAQ,QACjB,OAAO,QAAQ,mBAAmB,6BAA6B;EAIjE,IAAI,MAAM,QAAQ,SAAS,MAAM,QAAQ,aAAa,EAAE,mBACtD,OAAO,QAAQ,mBAAmB,wBAAwB;EAG5D,MAAM,eAAe,YAAY,UAAU,SAAS,QAAQ;EAC5D,IAAI,cACF,OAAO,QAAQ,mBAAmB,YAAY;EAIhD,OAAO,QAAQ,YAAY,eAAe;CAC5C;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkyc/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Decision engine, status transitions, and scoring logic",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@arkyc/types": "^2.
|
|
39
|
+
"@arkyc/types": "^2.3.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"typecheck": "tsc --noEmit",
|