@arkyc/core 1.1.0 → 1.2.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/LICENSE +21 -0
- package/dist/decision.d.mts +20 -1
- package/dist/decision.d.mts.map +1 -1
- package/dist/decision.mjs +3 -0
- package/dist/decision.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/normalize.d.mts +6 -1
- package/dist/normalize.d.mts.map +1 -1
- package/dist/normalize.mjs +14 -1
- package/dist/normalize.mjs.map +1 -1
- package/dist/risk.mjs +3 -2
- package/dist/risk.mjs.map +1 -1
- package/dist/status.d.mts.map +1 -1
- package/dist/status.mjs +9 -0
- package/dist/status.mjs.map +1 -1
- package/dist/thresholds.d.mts.map +1 -1
- package/dist/thresholds.mjs +2 -1
- package/dist/thresholds.mjs.map +1 -1
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Toneflix Technologies Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/decision.d.mts
CHANGED
|
@@ -24,11 +24,30 @@ interface DecisionFaceMatchInput {
|
|
|
24
24
|
/** Similarity between document portrait and selfie in [0, 1]. */
|
|
25
25
|
similarityScore: number;
|
|
26
26
|
}
|
|
27
|
+
/** Address-verification signals fed to the decision engine. */
|
|
28
|
+
interface DecisionAddressInput {
|
|
29
|
+
passed: boolean;
|
|
30
|
+
/** Overall address-verification confidence in [0, 1]. */
|
|
31
|
+
score: number;
|
|
32
|
+
/**
|
|
33
|
+
* What a failed result does: flag for `review` (default) or `reject`, driven
|
|
34
|
+
* by the workflow's address config. A low-confidence (but not failed) result
|
|
35
|
+
* is always review, never a reject.
|
|
36
|
+
*/
|
|
37
|
+
onFail?: 'review' | 'reject';
|
|
38
|
+
/**
|
|
39
|
+
* Workflow-configured minimum confidence to auto-verify the address. A passed
|
|
40
|
+
* address scoring below this routes to manual review. Falls back to the
|
|
41
|
+
* project `addressThreshold` when omitted.
|
|
42
|
+
*/
|
|
43
|
+
threshold?: number;
|
|
44
|
+
}
|
|
27
45
|
/** The complete set of signals the decision engine evaluates. */
|
|
28
46
|
interface DecisionInput {
|
|
29
47
|
document: DecisionDocumentInput;
|
|
30
48
|
liveness: DecisionLivenessInput;
|
|
31
49
|
faceMatch: DecisionFaceMatchInput;
|
|
50
|
+
address: DecisionAddressInput;
|
|
32
51
|
}
|
|
33
52
|
/** The decision engine's verdict for a session. */
|
|
34
53
|
interface DecisionResult {
|
|
@@ -57,5 +76,5 @@ declare class DecisionEngine {
|
|
|
57
76
|
static decide(input: DecisionInput, thresholds?: Partial<VerificationThresholds>): DecisionResult;
|
|
58
77
|
}
|
|
59
78
|
//#endregion
|
|
60
|
-
export { DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult };
|
|
79
|
+
export { DecisionAddressInput, DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult };
|
|
61
80
|
//# sourceMappingURL=decision.d.mts.map
|
package/dist/decision.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.d.mts","names":[],"sources":["../src/decision.ts"],"mappings":";;;;UAMiB,qBAAA;EAAA;EAEf,YAAA;;EAEA,aAAA;EAFA;EAIA,OAAA;AAAA;;UAIe,qBAAA;EACf,MAAA;EADoC;EAGpC,KAAA;EAHoC;EAKpC,aAAA;AAAA;;UAIe,sBAAA;EACf,MAAA;EADe;EAGf,eAAe;AAAA;;UAIA,aAAA;EACf,QAAA,EAAU,qBAAA;EACV,QAAA,EAAU,qBAAA;EACV,SAAA,EAAW,sBAAA;AAAA;;
|
|
1
|
+
{"version":3,"file":"decision.d.mts","names":[],"sources":["../src/decision.ts"],"mappings":";;;;UAMiB,qBAAA;EAAA;EAEf,YAAA;;EAEA,aAAA;EAFA;EAIA,OAAA;AAAA;;UAIe,qBAAA;EACf,MAAA;EADoC;EAGpC,KAAA;EAHoC;EAKpC,aAAA;AAAA;;UAIe,sBAAA;EACf,MAAA;EADe;EAGf,eAAe;AAAA;;UAIA,oBAAA;EACf,MAAA;EADmC;EAGnC,KAAA;EAHmC;;;;;EASnC,MAAA;EAMS;AAIX;;;;EAJE,SAAA;AAAA;;UAIe,aAAA;EACf,QAAA,EAAU,qBAAA;EACV,QAAA,EAAU,qBAAA;EACV,SAAA,EAAW,sBAAA;EACX,OAAA,EAAS,oBAAA;AAAA;;UAIM,cAAA;EACf,QAAA,EAAU,oBAAA;EACV,MAAA,EAAQ,cAAc;EANO;EAQ7B,SAAA;AAAA;;cAIW,cAAA;EANW;;;;;;;AAEb;AAIX;;;;;;;EANwB,OAsBf,MAAA,CAAO,KAAA,EAAO,aAAA,EAAe,UAAA,GAAa,OAAA,CAAQ,sBAAA,IAA0B,cAAA;AAAA"}
|
package/dist/decision.mjs
CHANGED
|
@@ -29,11 +29,14 @@ var DecisionEngine = class {
|
|
|
29
29
|
if (input.document.expired) return verdict("rejected", "DOCUMENT_EXPIRED");
|
|
30
30
|
if (!input.liveness.passed) return verdict("rejected", "LIVENESS_FAILED");
|
|
31
31
|
if (!input.faceMatch.passed) return verdict("rejected", "FACE_MATCH_FAILED");
|
|
32
|
+
if (!input.address.passed && input.address.onFail === "reject") return verdict("rejected", "ADDRESS_VERIFICATION_FAILED");
|
|
32
33
|
if (input.liveness.multipleFaces === true) return verdict("requires_review", "MULTIPLE_FACES_DETECTED");
|
|
33
34
|
if (input.document.qualityScore < t.documentQualityThreshold) return verdict("requires_review", "LOW_DOCUMENT_QUALITY");
|
|
34
35
|
if (input.document.ocrConfidence < t.ocrConfidenceThreshold) return verdict("requires_review", "OCR_LOW_CONFIDENCE");
|
|
35
36
|
if (input.liveness.score < t.livenessThreshold) return verdict("requires_review", "LIVENESS_LOW_CONFIDENCE");
|
|
36
37
|
if (input.faceMatch.similarityScore < t.faceMatchThreshold) return verdict("requires_review", "FACE_MATCH_LOW_CONFIDENCE");
|
|
38
|
+
if (!input.address.passed) return verdict("requires_review", "ADDRESS_VERIFICATION_FAILED");
|
|
39
|
+
if (input.address.score < (input.address.threshold ?? t.addressThreshold)) return verdict("requires_review", "ADDRESS_LOW_CONFIDENCE");
|
|
37
40
|
return verdict("approved", "AUTO_APPROVED");
|
|
38
41
|
}
|
|
39
42
|
};
|
package/dist/decision.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.mjs","names":[],"sources":["../src/decision.ts"],"sourcesContent":["import type { DecisionReason, VerificationDecision, VerificationThresholds } from '@arkyc/types'\n\nimport { Risk } from './risk'\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/** The complete set of signals the decision engine evaluates. */\nexport interface DecisionInput {\n document: DecisionDocumentInput\n liveness: DecisionLivenessInput\n faceMatch: DecisionFaceMatchInput\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 * Review precedence: multiple faces → low quality → low OCR\n * → low liveness → low face-match\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 if (!input.faceMatch.passed) {\n return verdict('rejected', 'FACE_MATCH_FAILED')\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\n // --- All checks cleared ---\n return verdict('approved', 'AUTO_APPROVED')\n }\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"decision.mjs","names":[],"sources":["../src/decision.ts"],"sourcesContent":["import type { DecisionReason, VerificationDecision, VerificationThresholds } from '@arkyc/types'\n\nimport { Risk } from './risk'\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}\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 * Review precedence: multiple faces → low quality → low OCR\n * → low liveness → low face-match\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 if (!input.faceMatch.passed) {\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 // --- 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 // --- All checks cleared ---\n return verdict('approved', 'AUTO_APPROVED')\n }\n}\n"],"mappings":";;;;AAmEA,IAAa,iBAAb,MAA4B;;;;;;;;;;;;;;;;CAgB1B,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;EAE9C,IAAI,CAAC,MAAM,UAAU,QACnB,OAAO,QAAQ,YAAY,mBAAmB;EAIhD,IAAI,CAAC,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,UACpD,OAAO,QAAQ,YAAY,6BAA6B;EAI1D,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;EAI5D,OAAO,QAAQ,YAAY,eAAe;CAC5C;AACF"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Thresholds } from "./thresholds.mjs";
|
|
2
2
|
import { InvalidStatusTransitionError, StatusMachine } from "./status.mjs";
|
|
3
|
-
import { DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult } from "./decision.mjs";
|
|
3
|
+
import { DecisionAddressInput, DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult } from "./decision.mjs";
|
|
4
4
|
import { Risk } from "./risk.mjs";
|
|
5
5
|
import { SessionRules } from "./session.mjs";
|
|
6
6
|
import { Normalize, SessionSignals } from "./normalize.mjs";
|
|
7
7
|
import { OrganizationContext, OrganizationProjectContext, OrganizationScopeError } from "./context.mjs";
|
|
8
|
-
export { DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult, InvalidStatusTransitionError, Normalize, OrganizationContext, OrganizationProjectContext, OrganizationScopeError, Risk, SessionRules, SessionSignals, StatusMachine, Thresholds };
|
|
8
|
+
export { DecisionAddressInput, DecisionDocumentInput, DecisionEngine, DecisionFaceMatchInput, DecisionInput, DecisionLivenessInput, DecisionResult, InvalidStatusTransitionError, Normalize, OrganizationContext, OrganizationProjectContext, OrganizationScopeError, Risk, SessionRules, SessionSignals, StatusMachine, Thresholds };
|
package/dist/normalize.d.mts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { DecisionInput } from "./decision.mjs";
|
|
2
|
-
import { FaceMatchResultData, IsoDateTime, LivenessResultData, OcrResultData, WebhookChecks } from "@arkyc/types";
|
|
2
|
+
import { AddressOnFail, AddressResultData, FaceMatchResultData, IsoDateTime, LivenessResultData, OcrResultData, WebhookChecks } from "@arkyc/types";
|
|
3
3
|
|
|
4
4
|
//#region src/normalize.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* The raw provider outputs gathered for a session, plus the document quality.
|
|
7
|
+
* `address` is only present when the (opt-in) address stage ran.
|
|
7
8
|
*/
|
|
8
9
|
interface SessionSignals {
|
|
9
10
|
documentQualityScore: number;
|
|
10
11
|
ocr: OcrResultData;
|
|
11
12
|
liveness: LivenessResultData;
|
|
12
13
|
faceMatch: FaceMatchResultData;
|
|
14
|
+
/** Address-verification result, when the address stage ran. */
|
|
15
|
+
address?: AddressResultData;
|
|
16
|
+
/** What a failed address does, from the workflow's address config. */
|
|
17
|
+
addressOnFail?: AddressOnFail;
|
|
13
18
|
}
|
|
14
19
|
/** Normalisation of gathered provider results into engine/webhook shapes. */
|
|
15
20
|
declare class Normalize {
|
package/dist/normalize.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.d.mts","names":[],"sources":["../src/normalize.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"normalize.d.mts","names":[],"sources":["../src/normalize.ts"],"mappings":";;;;;;AAiBA;;UAAiB,cAAA;EACf,oBAAA;EACA,GAAA,EAAK,aAAA;EACL,QAAA,EAAU,kBAAA;EACV,SAAA,EAAW,mBAAA;EAIK;EAFhB,OAAA,GAAU,iBAAA;EAEmB;EAA7B,aAAA,GAAgB,aAAA;AAAA;;cAIL,SAAA;EATD;;;;;;;;AAKmB;EALnB,OAmBH,eAAA,CAAgB,OAAA,EAAS,cAAA,EAAgB,GAAA,EAAK,WAAA,GAAc,IAAA,GAAO,aAAA;EAVtD;;;;;;;;EAAA,OAyCb,eAAA,CAAgB,OAAA,EAAS,cAAA,EAAgB,GAAA,EAAK,WAAA,GAAc,IAAA,GAAO,aAAA;AAAA"}
|
package/dist/normalize.mjs
CHANGED
|
@@ -26,6 +26,14 @@ var Normalize = class {
|
|
|
26
26
|
faceMatch: {
|
|
27
27
|
passed: signals.faceMatch.passed,
|
|
28
28
|
similarityScore: signals.faceMatch.similarityScore
|
|
29
|
+
},
|
|
30
|
+
address: signals.address ? {
|
|
31
|
+
passed: signals.address.passed,
|
|
32
|
+
score: signals.address.score,
|
|
33
|
+
onFail: signals.addressOnFail
|
|
34
|
+
} : {
|
|
35
|
+
passed: true,
|
|
36
|
+
score: 1
|
|
29
37
|
}
|
|
30
38
|
};
|
|
31
39
|
}
|
|
@@ -51,7 +59,12 @@ var Normalize = class {
|
|
|
51
59
|
face_match: {
|
|
52
60
|
passed: signals.faceMatch.passed,
|
|
53
61
|
similarity_score: signals.faceMatch.similarityScore
|
|
54
|
-
}
|
|
62
|
+
},
|
|
63
|
+
...signals.address ? { address: {
|
|
64
|
+
passed: signals.address.passed,
|
|
65
|
+
score: signals.address.score,
|
|
66
|
+
methods: signals.address.methods.map((m) => m.method)
|
|
67
|
+
} } : {}
|
|
55
68
|
};
|
|
56
69
|
}
|
|
57
70
|
};
|
package/dist/normalize.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.mjs","names":[],"sources":["../src/normalize.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"normalize.mjs","names":[],"sources":["../src/normalize.ts"],"sourcesContent":["import type {\n AddressOnFail,\n AddressResultData,\n FaceMatchResultData,\n IsoDateTime,\n LivenessResultData,\n OcrResultData,\n WebhookChecks,\n} from '@arkyc/types'\n\nimport type { DecisionInput } from './decision'\nimport { SessionRules } from './session'\n\n/**\n * The raw provider outputs gathered for a session, plus the document quality.\n * `address` is only present when the (opt-in) address stage ran.\n */\nexport interface SessionSignals {\n documentQualityScore: number\n ocr: OcrResultData\n liveness: LivenessResultData\n faceMatch: FaceMatchResultData\n /** Address-verification result, when the address stage ran. */\n address?: AddressResultData\n /** What a failed address does, from the workflow's address config. */\n addressOnFail?: AddressOnFail\n}\n\n/** Normalisation of gathered provider results into engine/webhook shapes. */\nexport class Normalize {\n /**\n * Normalise gathered provider results into the flat {@link DecisionInput} the\n * decision engine consumes. Document expiry is derived from the OCR `expiryDate`\n * field relative to `now`.\n *\n * @param signals\n * @param now\n * @returns\n */\n static toDecisionInput(signals: SessionSignals, now: IsoDateTime | Date): DecisionInput {\n return {\n document: {\n qualityScore: signals.documentQualityScore,\n ocrConfidence: signals.ocr.confidence,\n expired: SessionRules.isDocumentExpired(signals.ocr.fields.expiryDate, now),\n },\n liveness: {\n passed: signals.liveness.passed,\n score: signals.liveness.score,\n multipleFaces: signals.liveness.spoofSignals.multipleFaces === true,\n },\n faceMatch: {\n passed: signals.faceMatch.passed,\n similarityScore: signals.faceMatch.similarityScore,\n },\n // Absent address stage passes through so it can't veto a decision.\n address: signals.address\n ? { passed: signals.address.passed, score: signals.address.score, onFail: signals.addressOnFail }\n : { passed: true, score: 1 },\n }\n }\n\n /**\n * Build the `checks` summary embedded in webhook payloads from the same\n * gathered signals.\n *\n * @param signals\n * @param now\n * @returns\n */\n static toWebhookChecks(signals: SessionSignals, now: IsoDateTime | Date): WebhookChecks {\n return {\n document: {\n quality_score: signals.documentQualityScore,\n ocr_confidence: signals.ocr.confidence,\n expired: SessionRules.isDocumentExpired(signals.ocr.fields.expiryDate, now),\n },\n liveness: {\n passed: signals.liveness.passed,\n score: signals.liveness.score,\n },\n face_match: {\n passed: signals.faceMatch.passed,\n similarity_score: signals.faceMatch.similarityScore,\n },\n ...(signals.address\n ? {\n address: {\n passed: signals.address.passed,\n score: signals.address.score,\n methods: signals.address.methods.map((m) => m.method),\n },\n }\n : {}),\n }\n }\n}\n"],"mappings":";;;AA6BA,IAAa,YAAb,MAAuB;;;;;;;;;;CAUrB,OAAO,gBAAgB,SAAyB,KAAwC;EACtF,OAAO;GACL,UAAU;IACR,cAAc,QAAQ;IACtB,eAAe,QAAQ,IAAI;IAC3B,SAAS,aAAa,kBAAkB,QAAQ,IAAI,OAAO,YAAY,GAAG;GAC5E;GACA,UAAU;IACR,QAAQ,QAAQ,SAAS;IACzB,OAAO,QAAQ,SAAS;IACxB,eAAe,QAAQ,SAAS,aAAa,kBAAkB;GACjE;GACA,WAAW;IACT,QAAQ,QAAQ,UAAU;IAC1B,iBAAiB,QAAQ,UAAU;GACrC;GAEA,SAAS,QAAQ,UACb;IAAE,QAAQ,QAAQ,QAAQ;IAAQ,OAAO,QAAQ,QAAQ;IAAO,QAAQ,QAAQ;GAAc,IAC9F;IAAE,QAAQ;IAAM,OAAO;GAAE;EAC/B;CACF;;;;;;;;;CAUA,OAAO,gBAAgB,SAAyB,KAAwC;EACtF,OAAO;GACL,UAAU;IACR,eAAe,QAAQ;IACvB,gBAAgB,QAAQ,IAAI;IAC5B,SAAS,aAAa,kBAAkB,QAAQ,IAAI,OAAO,YAAY,GAAG;GAC5E;GACA,UAAU;IACR,QAAQ,QAAQ,SAAS;IACzB,OAAO,QAAQ,SAAS;GAC1B;GACA,YAAY;IACV,QAAQ,QAAQ,UAAU;IAC1B,kBAAkB,QAAQ,UAAU;GACtC;GACA,GAAI,QAAQ,UACR,EACE,SAAS;IACP,QAAQ,QAAQ,QAAQ;IACxB,OAAO,QAAQ,QAAQ;IACvB,SAAS,QAAQ,QAAQ,QAAQ,KAAK,MAAM,EAAE,MAAM;GACtD,EACF,IACA,CAAC;EACP;CACF;AACF"}
|
package/dist/risk.mjs
CHANGED
|
@@ -32,9 +32,10 @@ var Risk = class Risk {
|
|
|
32
32
|
input.document.qualityScore,
|
|
33
33
|
input.document.ocrConfidence,
|
|
34
34
|
input.liveness.score,
|
|
35
|
-
input.faceMatch.similarityScore
|
|
35
|
+
input.faceMatch.similarityScore,
|
|
36
|
+
input.address.score
|
|
36
37
|
].map(Risk.clamp01));
|
|
37
|
-
if (input.document.expired || !input.liveness.passed || !input.faceMatch.passed || input.liveness.multipleFaces === true) risk = Math.max(risk, .9);
|
|
38
|
+
if (input.document.expired || !input.liveness.passed || !input.faceMatch.passed || input.liveness.multipleFaces === true || !input.address.passed && input.address.onFail === "reject") risk = Math.max(risk, .9);
|
|
38
39
|
return Risk.clamp01(risk);
|
|
39
40
|
}
|
|
40
41
|
};
|
package/dist/risk.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"risk.mjs","names":[],"sources":["../src/risk.ts"],"sourcesContent":["import type { DecisionInput } from './decision'\n\n/** Average of a list of numbers; 0 for an empty list. */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0\n return values.reduce((sum, v) => sum + v, 0) / values.length\n}\n\n/** Aggregate risk scoring for a verification session. */\nexport class Risk {\n /**\n * Clamp a number into the [0, 1] range.\n *\n * @param value\n * @returns\n */\n static clamp01(value: number): number {\n if (Number.isNaN(value)) return 0\n return Math.min(1, Math.max(0, value))\n }\n\n /**\n * Compute an aggregate risk score in [0, 1] for a session (higher = riskier).\n *\n * The baseline is the inverse of the mean of the positive signals (document\n * quality, OCR confidence, liveness score, face-match similarity). Hard failure\n * signals (expired document, failed liveness/face-match, multiple faces) floor\n * the risk near the top of the range so risky sessions never look safe.\n *\n * @param input\n * @returns\n */\n static score(input: DecisionInput): number {\n const positives = [\n input.document.qualityScore,\n input.document.ocrConfidence,\n input.liveness.score,\n input.faceMatch.similarityScore,\n ].map(Risk.clamp01)\n\n let risk = 1 - mean(positives)\n\n const hardFailure =\n input.document.expired ||\n !input.liveness.passed ||\n !input.faceMatch.passed ||\n input.liveness.multipleFaces === true\n\n if (hardFailure) {\n risk = Math.max(risk, 0.9)\n }\n\n return Risk.clamp01(risk)\n }\n}\n"],"mappings":";;AAGA,SAAS,KAAK,QAA0B;CACtC,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO,OAAO,QAAQ,KAAK,MAAM,MAAM,GAAG,CAAC,IAAI,OAAO;AACxD;;AAGA,IAAa,OAAb,MAAa,KAAK;;;;;;;CAOhB,OAAO,QAAQ,OAAuB;EACpC,IAAI,OAAO,MAAM,KAAK,GAAG,OAAO;EAChC,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;CACvC;;;;;;;;;;;;CAaA,OAAO,MAAM,OAA8B;
|
|
1
|
+
{"version":3,"file":"risk.mjs","names":[],"sources":["../src/risk.ts"],"sourcesContent":["import type { DecisionInput } from './decision'\n\n/** Average of a list of numbers; 0 for an empty list. */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0\n return values.reduce((sum, v) => sum + v, 0) / values.length\n}\n\n/** Aggregate risk scoring for a verification session. */\nexport class Risk {\n /**\n * Clamp a number into the [0, 1] range.\n *\n * @param value\n * @returns\n */\n static clamp01(value: number): number {\n if (Number.isNaN(value)) return 0\n return Math.min(1, Math.max(0, value))\n }\n\n /**\n * Compute an aggregate risk score in [0, 1] for a session (higher = riskier).\n *\n * The baseline is the inverse of the mean of the positive signals (document\n * quality, OCR confidence, liveness score, face-match similarity). Hard failure\n * signals (expired document, failed liveness/face-match, multiple faces) floor\n * the risk near the top of the range so risky sessions never look safe.\n *\n * @param input\n * @returns\n */\n static score(input: DecisionInput): number {\n const positives = [\n input.document.qualityScore,\n input.document.ocrConfidence,\n input.liveness.score,\n input.faceMatch.similarityScore,\n input.address.score,\n ].map(Risk.clamp01)\n\n let risk = 1 - mean(positives)\n\n const hardFailure =\n input.document.expired ||\n !input.liveness.passed ||\n !input.faceMatch.passed ||\n input.liveness.multipleFaces === true ||\n // A failed address only floors risk when it's a rejecting failure.\n (!input.address.passed && input.address.onFail === 'reject')\n\n if (hardFailure) {\n risk = Math.max(risk, 0.9)\n }\n\n return Risk.clamp01(risk)\n }\n}\n"],"mappings":";;AAGA,SAAS,KAAK,QAA0B;CACtC,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO,OAAO,QAAQ,KAAK,MAAM,MAAM,GAAG,CAAC,IAAI,OAAO;AACxD;;AAGA,IAAa,OAAb,MAAa,KAAK;;;;;;;CAOhB,OAAO,QAAQ,OAAuB;EACpC,IAAI,OAAO,MAAM,KAAK,GAAG,OAAO;EAChC,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;CACvC;;;;;;;;;;;;CAaA,OAAO,MAAM,OAA8B;EASzC,IAAI,OAAO,IAAI,KARG;GAChB,MAAM,SAAS;GACf,MAAM,SAAS;GACf,MAAM,SAAS;GACf,MAAM,UAAU;GAChB,MAAM,QAAQ;EAChB,CAAC,CAAC,IAAI,KAAK,OAEiB,CAAC;EAU7B,IAPE,MAAM,SAAS,WACf,CAAC,MAAM,SAAS,UAChB,CAAC,MAAM,UAAU,UACjB,MAAM,SAAS,kBAAkB,QAEhC,CAAC,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,UAGnD,OAAO,KAAK,IAAI,MAAM,EAAG;EAG3B,OAAO,KAAK,QAAQ,IAAI;CAC1B;AACF"}
|
package/dist/status.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.mts","names":[],"sources":["../src/status.ts"],"mappings":";;;;;AAKA;cAAa,4BAAA,SAAqC,KAAA;EAAA,SAE9B,IAAA,EAAM,kBAAA;EAAA,SACN,EAAA,EAAI,kBAAA;cADJ,IAAA,EAAM,kBAAA,EACN,EAAA,EAAI,kBAAA;AAAA;;;;cAUX,aAAA;EAbqC;;;;;;;;;;EAAA,gBAwBhC,WAAA,EAAa,QAAA,CAAS,MAAA,CAAO,kBAAA,WAA6B,kBAAA;EArBlC;EAAA,
|
|
1
|
+
{"version":3,"file":"status.d.mts","names":[],"sources":["../src/status.ts"],"mappings":";;;;;AAKA;cAAa,4BAAA,SAAqC,KAAA;EAAA,SAE9B,IAAA,EAAM,kBAAA;EAAA,SACN,EAAA,EAAI,kBAAA;cADJ,IAAA,EAAM,kBAAA,EACN,EAAA,EAAI,kBAAA;AAAA;;;;cAUX,aAAA;EAbqC;;;;;;;;;;EAAA,gBAwBhC,WAAA,EAAa,QAAA,CAAS,MAAA,CAAO,kBAAA,WAA6B,kBAAA;EArBlC;EAAA,gBAwCxB,QAAA,WAAmB,kBAAA;EA9BX;;;;;;EAAA,OAsCjB,UAAA,CAAW,MAAA,EAAQ,kBAAA;EAAA;;;;;;;EAAA,OAWnB,aAAA,CAAc,IAAA,EAAM,kBAAA,EAAoB,EAAA,EAAI,kBAAA;EAtCnC;;;;;;;;EAAA,OAkDT,MAAA,CAAO,IAAA,EAAM,kBAAA,EAAoB,EAAA,EAAI,kBAAA,GAAqB,kBAAA;AAAA"}
|
package/dist/status.mjs
CHANGED
|
@@ -34,11 +34,19 @@ var StatusMachine = class StatusMachine {
|
|
|
34
34
|
],
|
|
35
35
|
started: [
|
|
36
36
|
"document_submitted",
|
|
37
|
+
"address_submitted",
|
|
37
38
|
"liveness_submitted",
|
|
38
39
|
"expired",
|
|
39
40
|
"cancelled"
|
|
40
41
|
],
|
|
41
42
|
document_submitted: [
|
|
43
|
+
"address_submitted",
|
|
44
|
+
"liveness_submitted",
|
|
45
|
+
"processing",
|
|
46
|
+
"expired",
|
|
47
|
+
"cancelled"
|
|
48
|
+
],
|
|
49
|
+
address_submitted: [
|
|
42
50
|
"liveness_submitted",
|
|
43
51
|
"processing",
|
|
44
52
|
"expired",
|
|
@@ -61,6 +69,7 @@ var StatusMachine = class StatusMachine {
|
|
|
61
69
|
"rejected",
|
|
62
70
|
"started",
|
|
63
71
|
"document_submitted",
|
|
72
|
+
"address_submitted",
|
|
64
73
|
"cancelled"
|
|
65
74
|
],
|
|
66
75
|
approved: [],
|
package/dist/status.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.mjs","names":[],"sources":["../src/status.ts"],"sourcesContent":["import type { VerificationStatus } from '@arkyc/types'\n\n/**\n * Thrown when an illegal status transition is attempted.\n */\nexport class InvalidStatusTransitionError extends Error {\n constructor(\n public readonly from: VerificationStatus,\n public readonly to: VerificationStatus,\n ) {\n super(`Invalid verification status transition: ${from} → ${to}`)\n this.name = 'InvalidStatusTransitionError'\n }\n}\n\n/**\n * Verification session status-transition rules.\n */\nexport class StatusMachine {\n /**\n * Allowed status transitions for a verification session.\n *\n * The happy path is:\n * pending → started → document_submitted → liveness_submitted → processing\n * → (approved | rejected | requires_review)\n *\n * `requires_review` can move to a terminal decision (manual) or back for a\n * retry. `expired` and `cancelled` are reachable from any non-terminal state.\n */\n static readonly TRANSITIONS: Readonly<Record<VerificationStatus, readonly VerificationStatus[]>> = {\n pending: ['started', 'expired', 'cancelled'],\n // `
|
|
1
|
+
{"version":3,"file":"status.mjs","names":[],"sources":["../src/status.ts"],"sourcesContent":["import type { VerificationStatus } from '@arkyc/types'\n\n/**\n * Thrown when an illegal status transition is attempted.\n */\nexport class InvalidStatusTransitionError extends Error {\n constructor(\n public readonly from: VerificationStatus,\n public readonly to: VerificationStatus,\n ) {\n super(`Invalid verification status transition: ${from} → ${to}`)\n this.name = 'InvalidStatusTransitionError'\n }\n}\n\n/**\n * Verification session status-transition rules.\n */\nexport class StatusMachine {\n /**\n * Allowed status transitions for a verification session.\n *\n * The happy path is:\n * pending → started → document_submitted → liveness_submitted → processing\n * → (approved | rejected | requires_review)\n *\n * `requires_review` can move to a terminal decision (manual) or back for a\n * retry. `expired` and `cancelled` are reachable from any non-terminal state.\n */\n static readonly TRANSITIONS: Readonly<Record<VerificationStatus, readonly VerificationStatus[]>> = {\n pending: ['started', 'expired', 'cancelled'],\n // Submit stages (`document`, `address`, `liveness`) are reorderable per\n // workflow, so a submit status can be reached directly from `started` and\n // flows forward into the next enabled submit stage or straight to `processing`.\n started: ['document_submitted', 'address_submitted', 'liveness_submitted', 'expired', 'cancelled'],\n document_submitted: ['address_submitted', 'liveness_submitted', 'processing', 'expired', 'cancelled'],\n // `address` is the opt-in stage — between document and liveness in canonical order.\n address_submitted: ['liveness_submitted', 'processing', 'expired', 'cancelled'],\n liveness_submitted: ['processing', 'expired', 'cancelled'],\n processing: ['approved', 'rejected', 'requires_review', 'expired', 'cancelled'],\n requires_review: ['approved', 'rejected', 'started', 'document_submitted', 'address_submitted', 'cancelled'],\n approved: [],\n rejected: [],\n expired: [],\n cancelled: [],\n }\n\n /** Statuses from which no further transition is possible. */\n static readonly TERMINAL: readonly VerificationStatus[] = ['approved', 'rejected', 'expired', 'cancelled']\n\n /**\n * Whether `status` is terminal (no further transitions allowed).\n *\n * @param status\n * @returns\n */\n static isTerminal(status: VerificationStatus): boolean {\n return StatusMachine.TERMINAL.includes(status)\n }\n\n /**\n * Whether moving from `from` to `to` is a permitted transition.\n *\n * @param from\n * @param to\n * @returns\n */\n static canTransition(from: VerificationStatus, to: VerificationStatus): boolean {\n return StatusMachine.TRANSITIONS[from].includes(to)\n }\n\n /**\n * Assert that `from → to` is permitted, throwing {@link InvalidStatusTransitionError}\n * otherwise. Returns `to` so it can be used inline.\n *\n * @param from\n * @param to\n * @returns\n */\n static assert(from: VerificationStatus, to: VerificationStatus): VerificationStatus {\n if (!StatusMachine.canTransition(from, to)) {\n throw new InvalidStatusTransitionError(from, to)\n }\n return to\n }\n}\n"],"mappings":";;;;AAKA,IAAa,+BAAb,cAAkD,MAAM;CAEpC;CACA;CAFlB,YACE,MACA,IACA;EACA,MAAM,2CAA2C,KAAK,KAAK,IAAI;EAH/C,KAAA,OAAA;EACA,KAAA,KAAA;EAGhB,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,gBAAb,MAAa,cAAc;;;;;;;;;;;CAWzB,OAAgB,cAAmF;EACjG,SAAS;GAAC;GAAW;GAAW;EAAW;EAI3C,SAAS;GAAC;GAAsB;GAAqB;GAAsB;GAAW;EAAW;EACjG,oBAAoB;GAAC;GAAqB;GAAsB;GAAc;GAAW;EAAW;EAEpG,mBAAmB;GAAC;GAAsB;GAAc;GAAW;EAAW;EAC9E,oBAAoB;GAAC;GAAc;GAAW;EAAW;EACzD,YAAY;GAAC;GAAY;GAAY;GAAmB;GAAW;EAAW;EAC9E,iBAAiB;GAAC;GAAY;GAAY;GAAW;GAAsB;GAAqB;EAAW;EAC3G,UAAU,CAAC;EACX,UAAU,CAAC;EACX,SAAS,CAAC;EACV,WAAW,CAAC;CACd;;CAGA,OAAgB,WAA0C;EAAC;EAAY;EAAY;EAAW;CAAW;;;;;;;CAQzG,OAAO,WAAW,QAAqC;EACrD,OAAO,cAAc,SAAS,SAAS,MAAM;CAC/C;;;;;;;;CASA,OAAO,cAAc,MAA0B,IAAiC;EAC9E,OAAO,cAAc,YAAY,KAAK,CAAC,SAAS,EAAE;CACpD;;;;;;;;;CAUA,OAAO,OAAO,MAA0B,IAA4C;EAClF,IAAI,CAAC,cAAc,cAAc,MAAM,EAAE,GACvC,MAAM,IAAI,6BAA6B,MAAM,EAAE;EAEjD,OAAO;CACT;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholds.d.mts","names":[],"sources":["../src/thresholds.ts"],"mappings":";;;;cAGa,UAAA;EAAA;;;;EAAA,gBAKK,QAAA,EAAU,sBAAA;
|
|
1
|
+
{"version":3,"file":"thresholds.d.mts","names":[],"sources":["../src/thresholds.ts"],"mappings":";;;;cAGa,UAAA;EAAA;;;;EAAA,gBAKK,QAAA,EAAU,sBAAA;EAcC;;;;;;EAAA,OAApB,OAAA,CAAQ,SAAA,GAAY,OAAA,CAAQ,sBAAA,IAA0B,sBAAA;AAAA"}
|
package/dist/thresholds.mjs
CHANGED
|
@@ -9,7 +9,8 @@ var Thresholds = class Thresholds {
|
|
|
9
9
|
documentQualityThreshold: .75,
|
|
10
10
|
ocrConfidenceThreshold: .8,
|
|
11
11
|
livenessThreshold: .85,
|
|
12
|
-
faceMatchThreshold: .75
|
|
12
|
+
faceMatchThreshold: .75,
|
|
13
|
+
addressThreshold: .6
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Merge a partial set of project overrides over the platform defaults.
|
package/dist/thresholds.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholds.mjs","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { VerificationThresholds } from '@arkyc/types'\n\n/** Verification threshold defaults and override resolution. */\nexport class Thresholds {\n /**\n * Platform default verification thresholds. Projects may override any subset\n * via `ProjectSettings.thresholds`; {@link Thresholds.resolve} merges them.\n */\n static readonly DEFAULTS: VerificationThresholds = {\n documentQualityThreshold: 0.75,\n ocrConfidenceThreshold: 0.8,\n livenessThreshold: 0.85,\n faceMatchThreshold: 0.75,\n }\n\n /**\n * Merge a partial set of project overrides over the platform defaults.\n *\n * @param overrides\n * @returns\n */\n static resolve(overrides?: Partial<VerificationThresholds>): VerificationThresholds {\n return { ...Thresholds.DEFAULTS, ...(overrides ?? {}) }\n }\n}\n"],"mappings":";;AAGA,IAAa,aAAb,MAAa,WAAW;;;;;CAKtB,OAAgB,WAAmC;EACjD,0BAA0B;EAC1B,wBAAwB;EACxB,mBAAmB;EACnB,oBAAoB;
|
|
1
|
+
{"version":3,"file":"thresholds.mjs","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { VerificationThresholds } from '@arkyc/types'\n\n/** Verification threshold defaults and override resolution. */\nexport class Thresholds {\n /**\n * Platform default verification thresholds. Projects may override any subset\n * via `ProjectSettings.thresholds`; {@link Thresholds.resolve} merges them.\n */\n static readonly DEFAULTS: VerificationThresholds = {\n documentQualityThreshold: 0.75,\n ocrConfidenceThreshold: 0.8,\n livenessThreshold: 0.85,\n faceMatchThreshold: 0.75,\n addressThreshold: 0.6,\n }\n\n /**\n * Merge a partial set of project overrides over the platform defaults.\n *\n * @param overrides\n * @returns\n */\n static resolve(overrides?: Partial<VerificationThresholds>): VerificationThresholds {\n return { ...Thresholds.DEFAULTS, ...(overrides ?? {}) }\n }\n}\n"],"mappings":";;AAGA,IAAa,aAAb,MAAa,WAAW;;;;;CAKtB,OAAgB,WAAmC;EACjD,0BAA0B;EAC1B,wBAAwB;EACxB,mBAAmB;EACnB,oBAAoB;EACpB,kBAAkB;CACpB;;;;;;;CAQA,OAAO,QAAQ,WAAqE;EAClF,OAAO;GAAE,GAAG,WAAW;GAAU,GAAI,aAAa,CAAC;EAAG;CACxD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkyc/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Decision engine, status transitions, and scoring logic",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@arkyc/types": "^1.
|
|
20
|
+
"@arkyc/types": "^1.2.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"typecheck": "tsc --noEmit",
|