@sekyro/web 0.1.0-rc.4 → 0.1.0-rc.5
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/CHANGELOG.md +20 -0
- package/README.md +44 -0
- package/dist/{capture-session-v3-uXJr1Wlm.d.ts → capture-session-v3-Okq0EQHG.d.ts} +9 -2
- package/dist/{capture-session-v3-DEInCOUg.d.cts → capture-session-v3-UQU_WjO2.d.cts} +9 -2
- package/dist/{chunk-TBANUT3X.js → chunk-SGDXLAXR.js} +286 -8
- package/dist/chunk-SGDXLAXR.js.map +1 -0
- package/dist/{chunk-EWRNPKTR.js → chunk-TG5IE552.js} +7 -2
- package/dist/{chunk-EWRNPKTR.js.map → chunk-TG5IE552.js.map} +1 -1
- package/dist/core/index.cjs +6 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/{create-verification-Dkb2tXuY.d.cts → create-verification-CA-3szPI.d.cts} +84 -7
- package/dist/{create-verification-Ce1y5kJR.d.ts → create-verification-c98uAxbl.d.ts} +84 -7
- package/dist/index.cjs +331 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +61 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +208 -7
- package/dist/internal/index.cjs.map +1 -1
- package/dist/internal/index.d.cts +3 -3
- package/dist/internal/index.d.ts +3 -3
- package/dist/internal/index.js +12 -2
- package/dist/internal/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-TBANUT3X.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { p as DeviceClass, i as CaptureState, b as CaptureArtifacts, c as CaptureErrorKind } from '../capture-session-v3-UQU_WjO2.cjs';
|
|
2
2
|
export { C as CENTERED_FACTOR, a as CIRCLE_RADIUS_FACTOR, I as IN_CIRCLE_FACTOR } from '../capture-constraints-BujkZcfb.cjs';
|
|
3
|
-
import {
|
|
4
|
-
export { A as AttestationError,
|
|
3
|
+
import { o as VerificationResult } from '../create-verification-CA-3szPI.cjs';
|
|
4
|
+
export { A as AttestationError, w as CLIP_MIME_ALLOWLIST, x as CaptureEngine, H as HostedClientLike, y as LivenessCaptureInternalHandle, z as LivenessCaptureInternals, B as LivenessFlowDeps, E as LivenessFlowOutcome, F as PRELOAD_BLAZE_INIT_TIMEOUT_MS, I as PRELOAD_BUDGET_MS, J as PROCESSING_POLL_DELAY_MS, K as PROCESSING_POLL_MAX, M as REGISTRY_RETRY_DELAY_MS, N as SubmitExecutor, O as SubmitRuntime, T as TRANSPORT_REPLAY_ATTEMPTS, Q as TRANSPORT_REPLAY_DELAY_MS, U as VerificationInternals, W as createLivenessCaptureInternal, X as createVerificationInternal, Y as mapEngineState, Z as mapErrorKind, _ as mapGuidanceHint, $ as preloadedDetectorFactory, a0 as randomUuid, a1 as resetLivenessAssetsForTests, a2 as runLivenessFlow, a3 as runSubmission, a4 as setPreloadLoaderForTests } from '../create-verification-CA-3szPI.cjs';
|
|
5
5
|
import { C as CheckReason, c as HostedSessionView, d as HostedStepView, h as SessionView, b as CheckType } from '../hosted-client-DWzCRYJK.cjs';
|
|
6
6
|
import '../detect-worker-CKXHlXx9.cjs';
|
|
7
7
|
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { p as DeviceClass, i as CaptureState, b as CaptureArtifacts, c as CaptureErrorKind } from '../capture-session-v3-Okq0EQHG.js';
|
|
2
2
|
export { C as CENTERED_FACTOR, a as CIRCLE_RADIUS_FACTOR, I as IN_CIRCLE_FACTOR } from '../capture-constraints-BujkZcfb.js';
|
|
3
|
-
import {
|
|
4
|
-
export { A as AttestationError,
|
|
3
|
+
import { o as VerificationResult } from '../create-verification-c98uAxbl.js';
|
|
4
|
+
export { A as AttestationError, w as CLIP_MIME_ALLOWLIST, x as CaptureEngine, H as HostedClientLike, y as LivenessCaptureInternalHandle, z as LivenessCaptureInternals, B as LivenessFlowDeps, E as LivenessFlowOutcome, F as PRELOAD_BLAZE_INIT_TIMEOUT_MS, I as PRELOAD_BUDGET_MS, J as PROCESSING_POLL_DELAY_MS, K as PROCESSING_POLL_MAX, M as REGISTRY_RETRY_DELAY_MS, N as SubmitExecutor, O as SubmitRuntime, T as TRANSPORT_REPLAY_ATTEMPTS, Q as TRANSPORT_REPLAY_DELAY_MS, U as VerificationInternals, W as createLivenessCaptureInternal, X as createVerificationInternal, Y as mapEngineState, Z as mapErrorKind, _ as mapGuidanceHint, $ as preloadedDetectorFactory, a0 as randomUuid, a1 as resetLivenessAssetsForTests, a2 as runLivenessFlow, a3 as runSubmission, a4 as setPreloadLoaderForTests } from '../create-verification-c98uAxbl.js';
|
|
5
5
|
import { C as CheckReason, c as HostedSessionView, d as HostedStepView, h as SessionView, b as CheckType } from '../hosted-client-DWzCRYJK.js';
|
|
6
6
|
import '../detect-worker-CKXHlXx9.js';
|
|
7
7
|
|
package/dist/internal/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AttestationError,
|
|
3
3
|
CLIP_MIME_ALLOWLIST,
|
|
4
|
+
PRELOAD_BLAZE_INIT_TIMEOUT_MS,
|
|
5
|
+
PRELOAD_BUDGET_MS,
|
|
4
6
|
PROCESSING_POLL_DELAY_MS,
|
|
5
7
|
PROCESSING_POLL_MAX,
|
|
6
8
|
REGISTRY_RETRY_DELAY_MS,
|
|
@@ -21,12 +23,15 @@ import {
|
|
|
21
23
|
mapGuidanceHint,
|
|
22
24
|
mapSessionResult,
|
|
23
25
|
mergeSessionView,
|
|
26
|
+
preloadedDetectorFactory,
|
|
24
27
|
randomUuid,
|
|
28
|
+
resetLivenessAssetsForTests,
|
|
25
29
|
resetTurnstileForTests,
|
|
26
30
|
runLivenessFlow,
|
|
27
31
|
runSubmission,
|
|
32
|
+
setPreloadLoaderForTests,
|
|
28
33
|
stepOf
|
|
29
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-SGDXLAXR.js";
|
|
30
35
|
import {
|
|
31
36
|
CENTERED_FACTOR,
|
|
32
37
|
CIRCLE_RADIUS_FACTOR,
|
|
@@ -34,7 +39,7 @@ import {
|
|
|
34
39
|
classifyDevice,
|
|
35
40
|
mapGetUserMediaError,
|
|
36
41
|
probeDevice
|
|
37
|
-
} from "../chunk-
|
|
42
|
+
} from "../chunk-TG5IE552.js";
|
|
38
43
|
import "../chunk-YVFF4Q4G.js";
|
|
39
44
|
import "../chunk-NWF46H7K.js";
|
|
40
45
|
import {
|
|
@@ -608,6 +613,8 @@ export {
|
|
|
608
613
|
IN_CIRCLE_FACTOR,
|
|
609
614
|
MRZ_BAND_DEFAULTS,
|
|
610
615
|
MR_DOC_CATALOG,
|
|
616
|
+
PRELOAD_BLAZE_INIT_TIMEOUT_MS,
|
|
617
|
+
PRELOAD_BUDGET_MS,
|
|
611
618
|
PROCESSING_POLL_DELAY_MS,
|
|
612
619
|
PROCESSING_POLL_MAX,
|
|
613
620
|
REGISTRY_RETRY_DELAY_MS,
|
|
@@ -632,14 +639,17 @@ export {
|
|
|
632
639
|
mapGuidanceHint,
|
|
633
640
|
mapSessionResult,
|
|
634
641
|
mergeSessionView,
|
|
642
|
+
preloadedDetectorFactory,
|
|
635
643
|
quadAreaFrac,
|
|
636
644
|
quadBounds,
|
|
637
645
|
quadIou,
|
|
638
646
|
quadTiltRatio,
|
|
639
647
|
randomUuid,
|
|
648
|
+
resetLivenessAssetsForTests,
|
|
640
649
|
resetTurnstileForTests,
|
|
641
650
|
runLivenessFlow,
|
|
642
651
|
runSubmission,
|
|
652
|
+
setPreloadLoaderForTests,
|
|
643
653
|
specularFraction,
|
|
644
654
|
stepOf,
|
|
645
655
|
warmDocDetector
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/doc/mrz-band.ts","../../src/core/doc/frame-analysis.ts","../../src/core/doc/feedback-aggregator.ts","../../src/core/doc/capture-session-doc.ts"],"sourcesContent":["// MRZ-band detector (Task 37 round 3) — the passport path's detection signal.\n//\n// WHY: edge/contour detection (scanic) fails on passport data pages — an open\n// booklet's page edges are white-on-white, so quads lock late or onto the\n// wrong rectangle (operator field finding, twice). The industry-standard\n// approach (Scanbot/Regula/Dynamsoft; PyImageSearch's classic blackhat\n// pipeline; YOLO-MRZ) is to detect the MRZ ITSELF: two 44-char lines of\n// high-contrast OCR-B strokes — the one feature of a passport that is\n// reliably detectable in any framing. This is the pure-JS analog of the\n// blackhat+gradient pipeline: per-row horizontal-stroke density with a\n// column-coverage requirement, then the tallest qualifying row run.\n//\n// Deterministic, allocation-light, no wasm: ~2ms on a 480px frame in node —\n// cheap enough to run at full doc cadence on LOW devices.\n\nexport interface MrzBand {\n x: number;\n y: number;\n width: number;\n height: number;\n /** Mean qualifying-row stroke density (diagnostics/telemetry). */\n density: number;\n}\n\nexport interface MrzBandThresholds {\n /** Luma step that counts as a text stroke edge. */\n EDGE_LUMA_DELTA: number;\n /** Min fraction of a row's pixels that are stroke edges to qualify. */\n MIN_ROW_DENSITY: number;\n /** Row must have stroke edges spread across ≥ this fraction of 12 columns. */\n MIN_COLUMN_COVERAGE: number;\n /** Band width (stroke x-extent) as a fraction of frame width. */\n MIN_WIDTH_FRAC: number;\n /** Band height bounds as fractions of frame height (2–3 OCR-B lines). */\n MIN_HEIGHT_FRAC: number;\n MAX_HEIGHT_FRAC: number;\n /** Rows this close together merge into one band (inter-line gap). */\n ROW_MERGE_GAP: number;\n}\n\n/** Frozen defaults (goldens in test/doc/mrz-band.test.ts — retune together). */\nexport const MRZ_BAND_DEFAULTS: Readonly<MrzBandThresholds> = Object.freeze({\n EDGE_LUMA_DELTA: 40,\n MIN_ROW_DENSITY: 0.08,\n MIN_COLUMN_COVERAGE: 0.6,\n MIN_WIDTH_FRAC: 0.55,\n MIN_HEIGHT_FRAC: 0.02,\n MAX_HEIGHT_FRAC: 0.16,\n ROW_MERGE_GAP: 18,\n});\n\nconst COLUMN_BUCKETS = 12;\n\n/**\n * Detect the MRZ stroke band on a (≤480px) grayscale-able frame. Returns the\n * tight band rect in frame pixels, or null when no qualifying band exists\n * (no MRZ in view, or only a partial/narrow slice of one).\n */\nexport function detectMrzBand(\n frame: ImageData,\n overrides: Partial<MrzBandThresholds> = {},\n): MrzBand | null {\n const t = { ...MRZ_BAND_DEFAULTS, ...overrides };\n const { data, width, height } = frame;\n if (width < 32 || height < 32) return null;\n\n // Per-row: stroke-edge count + column coverage + x-extent.\n const qualifies = new Array<boolean>(height).fill(false);\n const density = new Float32Array(height);\n const minX = new Int32Array(height).fill(width);\n const maxX = new Int32Array(height).fill(-1);\n\n const bucketWidth = width / COLUMN_BUCKETS;\n const buckets = new Array<boolean>(COLUMN_BUCKETS);\n for (let y = 0; y < height; y++) {\n let edges = 0;\n buckets.fill(false);\n const rowOffset = y * width * 4;\n let previousLuma =\n 0.299 * (data[rowOffset] ?? 0) +\n 0.587 * (data[rowOffset + 1] ?? 0) +\n 0.114 * (data[rowOffset + 2] ?? 0);\n for (let x = 1; x < width; x++) {\n const i = rowOffset + x * 4;\n const luma =\n 0.299 * (data[i] ?? 0) + 0.587 * (data[i + 1] ?? 0) + 0.114 * (data[i + 2] ?? 0);\n if (Math.abs(luma - previousLuma) >= t.EDGE_LUMA_DELTA) {\n edges += 1;\n buckets[Math.min(COLUMN_BUCKETS - 1, Math.floor(x / bucketWidth))] = true;\n if (x < (minX[y] as number)) minX[y] = x;\n if (x > (maxX[y] as number)) maxX[y] = x;\n }\n previousLuma = luma;\n }\n density[y] = edges / width;\n const coverage = buckets.filter(Boolean).length / COLUMN_BUCKETS;\n qualifies[y] =\n density[y]! >= t.MIN_ROW_DENSITY &&\n coverage >= t.MIN_COLUMN_COVERAGE &&\n (maxX[y] as number) - (minX[y] as number) >= t.MIN_WIDTH_FRAC * width;\n }\n\n // Merge qualifying rows across the inter-line gap; keep the best band.\n let best: { top: number; bottom: number; rows: number } | null = null;\n let runTop = -1;\n let lastQualifying = -1;\n let runRows = 0;\n const flush = (bottom: number) => {\n if (runTop < 0) return;\n const bandHeight = bottom - runTop + 1;\n if (\n bandHeight >= t.MIN_HEIGHT_FRAC * height &&\n bandHeight <= t.MAX_HEIGHT_FRAC * height &&\n runRows >= 4 && // at least a few real stroke rows, not a lone edge\n (best === null || runRows > best.rows)\n ) {\n best = { top: runTop, bottom, rows: runRows };\n }\n runTop = -1;\n runRows = 0;\n };\n for (let y = 0; y < height; y++) {\n if (qualifies[y]) {\n if (runTop < 0) runTop = y;\n else if (y - lastQualifying > t.ROW_MERGE_GAP) {\n flush(lastQualifying);\n runTop = y;\n }\n lastQualifying = y;\n runRows += 1;\n }\n }\n flush(lastQualifying);\n if (best === null) return null;\n const band = best as { top: number; bottom: number; rows: number };\n\n // Tight x-extent + mean density over the band's qualifying rows.\n let x1 = width;\n let x2 = -1;\n let densitySum = 0;\n let densityRows = 0;\n for (let y = band.top; y <= band.bottom; y++) {\n if (!qualifies[y]) continue;\n if ((minX[y] as number) < x1) x1 = minX[y] as number;\n if ((maxX[y] as number) > x2) x2 = maxX[y] as number;\n densitySum += density[y] as number;\n densityRows += 1;\n }\n if (x2 - x1 < t.MIN_WIDTH_FRAC * width) return null;\n return {\n x: x1,\n y: band.top,\n width: x2 - x1,\n height: band.bottom - band.top + 1,\n density: densityRows > 0 ? densitySum / densityRows : 0,\n };\n}\n","// Doc frame analysis (capture v2, Task-37 round 4) — the BlinkID-inspired\n// PER-FRAME status vector: independent framing / lighting / blur / glare /\n// tilt verdicts instead of a single collapsed guidance key. Pure and\n// deterministic: inputs are the detection (quad or MRZ band rect-as-quad),\n// the doc-region crop's quality measures, and static thresholds — no clocks,\n// no I/O. The FeedbackAggregator votes over these statuses; DocGateV1's\n// integrator/stability logic consumes `framingOk`/`qualityOk`.\nimport { EXPOSURE_BAND } from \"../v3/frame-quality\";\nimport { aspectBand, type DocFormat } from \"./doc-catalog\";\nimport {\n DOC_GATE_V1_DEFAULTS,\n type DocGateThresholds,\n type DocQuad,\n quadAreaFrac,\n quadBounds,\n} from \"./doc-gate-v1\";\n\nexport type FramingStatus =\n | \"no_document\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"ok\";\n\nexport type LightingStatus = \"too_dark\" | \"too_bright\" | \"ok\";\nexport type BlurStatus = \"blurry\" | \"ok\";\nexport type GlareStatus = \"glare\" | \"ok\";\n\n/** The per-frame analysis vector (every axis judged independently). */\nexport interface DocFrameAnalysis {\n framing: FramingStatus;\n lighting: LightingStatus;\n blur: BlurStatus;\n glare: GlareStatus;\n /** All-clear: every axis ok — the gate's integrator/stability input. */\n acceptable: boolean;\n}\n\nexport interface DocFrameMeasures {\n /** frame-quality sharpness on the doc-region crop (0..1). */\n sharpness: number;\n /** frame-quality exposure on the doc-region crop (0..1). */\n exposure: number;\n /** Mean crop luma (0..255) — picks too_dark vs too_bright. */\n meanLuma: number;\n /** Fraction of crop pixels at/above the specular ceiling (0..1). */\n specularFraction: number;\n}\n\n/** Analysis thresholds beyond the gate's — frozen defaults (round 4). */\nexport const DOC_ANALYSIS_DEFAULTS = Object.freeze({\n /** Doc area fraction above which the camera is too close (capture would crop). */\n MAX_AREA_FRAC: 0.92,\n /** Opposite-side length ratio beyond which the view is too oblique. */\n TILT_MAX_SIDE_RATIO: 1.3,\n /** Specular-pixel fraction at/above which the frame reads as glare. */\n GLARE_MAX_SPECULAR_FRAC: 0.025,\n});\n\n/** Fraction of crop pixels with luma ≥ 252 — cheap specular-highlight measure. */\nexport function specularFraction(crop: ImageData): number {\n const { data, width, height } = crop;\n const pixels = width * height;\n if (pixels === 0) return 0;\n let hot = 0;\n for (let i = 0; i < pixels; i++) {\n const o = i * 4;\n // Rec.601 luma, integer-ish — a specular blowout saturates all channels.\n const luma =\n 0.299 * (data[o] ?? 0) + 0.587 * (data[o + 1] ?? 0) + 0.114 * (data[o + 2] ?? 0);\n if (luma >= 252) hot++;\n }\n return hot / pixels;\n}\n\n/** Opposite-side length ratio of the quad (1 = fronto-parallel). */\nexport function quadTiltRatio(quad: DocQuad): number {\n const len = (a: { x: number; y: number }, b: { x: number; y: number }) =>\n Math.hypot(b.x - a.x, b.y - a.y);\n const top = len(quad.topLeft, quad.topRight);\n const bottom = len(quad.bottomLeft, quad.bottomRight);\n const left = len(quad.topLeft, quad.bottomLeft);\n const right = len(quad.topRight, quad.bottomRight);\n const h = Math.max(top, bottom) / Math.max(1e-6, Math.min(top, bottom));\n const v = Math.max(left, right) / Math.max(1e-6, Math.min(left, right));\n return Math.max(h, v);\n}\n\n/**\n * Judge one frame. `detectionMode` mirrors the gate: ID-1 quads carry\n * area/aspect/tilt semantics; ID-3 MRZ bands carry width-fraction semantics\n * (band rects are axis-aligned, so tilt/aspect checks are band-specific).\n */\nexport function analyzeDocFrame(input: {\n format: DocFormat;\n quad: DocQuad | null;\n frameWidth: number;\n frameHeight: number;\n measures: DocFrameMeasures;\n thresholds?: Partial<DocGateThresholds>;\n}): DocFrameAnalysis {\n const t = { ...DOC_GATE_V1_DEFAULTS, ...input.thresholds };\n const a = DOC_ANALYSIS_DEFAULTS;\n const { quad, frameWidth, frameHeight, measures } = input;\n const bandMode = input.format === \"ID-3\";\n\n let framing: FramingStatus = \"ok\";\n if (quad === null) {\n framing = \"no_document\";\n } else if (bandMode) {\n const bounds = quadBounds(quad);\n const widthFrac = frameWidth > 0 ? bounds.width / frameWidth : 0;\n const aspect = bounds.height > 0 ? bounds.width / bounds.height : 0;\n if (widthFrac < t.ID3_BAND_MIN_WIDTH_FRAC) framing = \"too_far\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (aspect < t.ID3_BAND_ASPECT_MIN || aspect > t.ID3_BAND_ASPECT_MAX) {\n framing = \"wrong_shape\";\n }\n } else {\n const areaFrac = quadAreaFrac(quad, frameWidth, frameHeight);\n const band = aspectBand(input.format);\n const bounds = quadBounds(quad);\n const aspect =\n Math.max(bounds.width, bounds.height) / Math.max(1e-6, Math.min(bounds.width, bounds.height));\n if (areaFrac < t.MIN_AREA_FRAC) framing = \"too_far\";\n else if (areaFrac > a.MAX_AREA_FRAC) framing = \"too_close\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (Math.abs(aspect - band.center) > band.tolerance) framing = \"wrong_shape\";\n else if (quadTiltRatio(quad) > a.TILT_MAX_SIDE_RATIO) framing = \"tilted\";\n }\n\n const lighting: LightingStatus =\n measures.exposure >= t.GUIDE_EXPOSURE\n ? \"ok\"\n : measures.meanLuma < (EXPOSURE_BAND[0] + EXPOSURE_BAND[1]) / 2\n ? \"too_dark\"\n : \"too_bright\";\n const blur: BlurStatus = measures.sharpness >= t.GUIDE_SHARPNESS ? \"ok\" : \"blurry\";\n const glare: GlareStatus =\n measures.specularFraction >= a.GLARE_MAX_SPECULAR_FRAC ? \"glare\" : \"ok\";\n\n return {\n framing,\n lighting,\n blur,\n glare,\n acceptable: framing === \"ok\" && lighting === \"ok\" && blur === \"ok\" && glare === \"ok\",\n };\n}\n\nfunction edgeClipped(\n quad: DocQuad,\n frameWidth: number,\n frameHeight: number,\n margin: number,\n): boolean {\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n return pts.some(\n (p) => p.x <= margin || p.y <= margin || p.x >= frameWidth - margin || p.y >= frameHeight - margin,\n );\n}\n","// Feedback aggregator (capture v2, Task-37 round 4) — the BlinkID-inspired\n// anti-flicker layer between per-frame analysis and the UI: each frame VOTES\n// for a feedback state; votes decay exponentially over a sliding window and\n// the DOMINANT state wins, but never before the current state's minimum\n// display duration has elapsed. Success states bypass voting entirely.\n//\n// 100% deterministic: the clock is an input (`nowMs`), the decay math is\n// closed-form — golden-testable tick sequences, no timers.\nimport type { DocFrameAnalysis } from \"./frame-analysis\";\n\n/** Feedback vocabulary (dcv2.* message keys on the hosted side). */\nexport type DocFeedbackKey =\n | \"sensing\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"too_dark\"\n | \"too_bright\"\n | \"blurry\"\n | \"glare\"\n | \"hold_still\"\n | \"processing\"\n | \"side_captured\"\n | \"captured\";\n\n/** Reticle rendering mode per feedback state (the stage maps to visuals). */\nexport type ReticleType = \"searching\" | \"coaching\" | \"locking\" | \"processing\" | \"flip\" | \"done\";\n\nexport interface DocFeedbackState {\n key: DocFeedbackKey;\n reticle: ReticleType;\n /** The state holds the UI at least this long (ms) before a switch. */\n minDurationMs: number;\n}\n\nexport const DOC_FEEDBACK_STATES: Readonly<Record<DocFeedbackKey, DocFeedbackState>> =\n Object.freeze({\n sensing: { key: \"sensing\", reticle: \"searching\", minDurationMs: 900 },\n too_far: { key: \"too_far\", reticle: \"coaching\", minDurationMs: 1400 },\n too_close: { key: \"too_close\", reticle: \"coaching\", minDurationMs: 1400 },\n edge_clipped: { key: \"edge_clipped\", reticle: \"coaching\", minDurationMs: 1400 },\n wrong_shape: { key: \"wrong_shape\", reticle: \"coaching\", minDurationMs: 1400 },\n tilted: { key: \"tilted\", reticle: \"coaching\", minDurationMs: 1400 },\n too_dark: { key: \"too_dark\", reticle: \"coaching\", minDurationMs: 1400 },\n too_bright: { key: \"too_bright\", reticle: \"coaching\", minDurationMs: 1400 },\n blurry: { key: \"blurry\", reticle: \"coaching\", minDurationMs: 1400 },\n glare: { key: \"glare\", reticle: \"coaching\", minDurationMs: 1400 },\n hold_still: { key: \"hold_still\", reticle: \"locking\", minDurationMs: 900 },\n processing: { key: \"processing\", reticle: \"processing\", minDurationMs: 800 },\n side_captured: { key: \"side_captured\", reticle: \"flip\", minDurationMs: 2600 },\n captured: { key: \"captured\", reticle: \"done\", minDurationMs: 800 },\n });\n\n/** Frame analysis → the feedback vote it casts (worst axis speaks). */\nexport function feedbackVoteOf(analysis: DocFrameAnalysis): DocFeedbackKey {\n if (analysis.framing === \"no_document\") return \"sensing\";\n if (analysis.framing === \"too_far\") return \"too_far\";\n if (analysis.framing === \"too_close\") return \"too_close\";\n if (analysis.framing === \"edge_clipped\") return \"edge_clipped\";\n if (analysis.framing === \"wrong_shape\") return \"wrong_shape\";\n if (analysis.framing === \"tilted\") return \"tilted\";\n if (analysis.lighting === \"too_dark\") return \"too_dark\";\n if (analysis.lighting === \"too_bright\") return \"too_bright\";\n if (analysis.glare === \"glare\") return \"glare\";\n if (analysis.blur === \"blurry\") return \"blurry\";\n return \"hold_still\";\n}\n\ninterface Vote {\n key: DocFeedbackKey;\n atMs: number;\n}\n\n/** Sliding vote window + exponential decay + per-state minimum hold. */\nexport class DocFeedbackAggregator {\n private votes: Vote[] = [];\n private current: DocFeedbackState = DOC_FEEDBACK_STATES.sensing;\n private currentSinceMs: number | null = null;\n\n constructor(\n private readonly windowMs = 2600,\n /** Per-second decay factor — recent frames outvote stale ones. */\n private readonly decayPerSecond = 0.35,\n ) {}\n\n reset(): void {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES.sensing;\n this.currentSinceMs = null;\n }\n\n /** Force a terminal/choreography state (capture fired, side flip, upload). */\n force(key: Extract<DocFeedbackKey, \"processing\" | \"side_captured\" | \"captured\">, nowMs: number): DocFeedbackState {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES[key];\n this.currentSinceMs = nowMs;\n return this.current;\n }\n\n /** Cast this frame's vote and return the (possibly held) winning state. */\n tick(vote: DocFeedbackKey, nowMs: number): DocFeedbackState {\n if (this.currentSinceMs === null) this.currentSinceMs = nowMs;\n this.votes.push({ key: vote, atMs: nowMs });\n this.votes = this.votes.filter((v) => nowMs - v.atMs <= this.windowMs);\n\n // Hold the current state through its minimum display duration.\n if (nowMs - this.currentSinceMs < this.current.minDurationMs) return this.current;\n\n const scores = new Map<DocFeedbackKey, number>();\n for (const v of this.votes) {\n const ageSeconds = (nowMs - v.atMs) / 1000;\n const weight = this.decayPerSecond ** ageSeconds; // 1 now → decays with age\n scores.set(v.key, (scores.get(v.key) ?? 0) + weight);\n }\n let winner: DocFeedbackKey = this.current.key;\n let best = -1;\n for (const [key, score] of scores) {\n if (score > best) {\n best = score;\n winner = key;\n }\n }\n if (winner !== this.current.key) {\n this.current = DOC_FEEDBACK_STATES[winner];\n this.currentSinceMs = nowMs;\n }\n return this.current;\n }\n}\n","// CaptureSessionDoc (Task 34) — the THIN document twin of CaptureSessionV3:\n// environment-facing camera, a 480px detect snapshot at the device profile's\n// cadence, DocGateV1 ticks, and on `ready` an UNCROPPED full-resolution still\n// (gate round 2: client-side quad cropping cut the MRZ when corners were off;\n// kyc-docs-v2 runs its own detector/warp — the client's job is timing, not\n// geometry). Same event contract SHAPE as CaptureSessionV3\n// (`statechange | checks | guidance | artifacts | error`) so hosts bind it\n// with the same hook pattern. Every effect is seam-injectable for tests; the\n// gate itself stays 100% deterministic (clock = tick input).\nimport { mapGetUserMediaError } from \"../media\";\nimport type { CaptureArtifacts, CaptureErrorKind, CaptureState } from \"../types\";\nimport { classifyDevice, type DeviceClass, probeDevice } from \"../v3/device-profile\";\nimport { exposure, meanLuma, sharpness } from \"../v3/frame-quality\";\nimport {\n DocFeedbackAggregator,\n type DocFeedbackState,\n feedbackVoteOf,\n} from \"./feedback-aggregator\";\nimport { analyzeDocFrame, type DocFrameAnalysis, specularFraction } from \"./frame-analysis\";\nimport type { DocFormat, DocSide } from \"./doc-catalog\";\nimport {\n DOC_DETECT_WIDTH,\n DocGateV1,\n type DocGateThresholds,\n type DocGateTick,\n type DocQuad,\n} from \"./doc-gate-v1\";\nimport { detectMrzBand } from \"./mrz-band\";\n\n/**\n * LAZY scanic load (route-budget contract): the vendored detector is ~39KiB gz\n * and must NOT ride the host's session-shell bundle — it is dynamically\n * imported on the FIRST detect tick (the camera-permission prompt gives the\n * fetch plenty of cover). Tests inject `opts.detect` and never touch this.\n */\nlet scanicDetect: ((image: ImageData) => Promise<DocQuad | null>) | null = null;\nasync function lazyScanicDetect(image: ImageData): Promise<DocQuad | null> {\n if (scanicDetect === null) {\n const module_ = await import(\"./detector-scanic\");\n scanicDetect = module_.detectQuad;\n }\n return scanicDetect(image);\n}\n\n/**\n * ID-3 detection (round 3): the MRZ stroke band, NOT page-edge quads — an\n * open booklet's page edges are white-on-white and defeat contour detection\n * (operator field finding, twice). The band rect becomes the gate's \"quad\"\n * (ID-3 band-mode geometry). Pure JS — no scanic on the passport path.\n */\nasync function mrzBandDetect(image: ImageData): Promise<DocQuad | null> {\n const band = detectMrzBand(image);\n if (band === null) return null;\n return {\n topLeft: { x: band.x, y: band.y },\n topRight: { x: band.x + band.width, y: band.y },\n bottomRight: { x: band.x + band.width, y: band.y + band.height },\n bottomLeft: { x: band.x, y: band.y + band.height },\n };\n}\n\n/**\n * Pre-fetch + cache the scanic chunk (call when the scan surface MOUNTS, before\n * the camera is even granted) so the first detect tick pays no chunk-fetch\n * latency. Fire-and-forget; failures fall back to the lazy path.\n */\nexport function warmDocDetector(): void {\n if (scanicDetect !== null) return;\n void import(\"./detector-scanic\")\n .then((module_) => {\n scanicDetect = module_.detectQuad;\n })\n .catch(() => undefined);\n}\n\n/** Doc detect cadence per device class — scanic @480px is ~3–8× a face tick,\n * so the doc loop paces slower than the face profiles (findings §1: 2–5 Hz). */\nexport const DOC_CADENCE_HZ: Readonly<Record<DeviceClass, number>> = Object.freeze({\n LOW: 3,\n MID: 5,\n HIGH: 6,\n});\n\nexport interface DocChecksPayload {\n engine: \"docv1\";\n /** Gate integrator 0..1 — the overlay ring. */\n p: number;\n plausible: boolean;\n stableTicks: number;\n quad: DocQuad | null;\n /** Detect-frame size the quad coordinates live in (overlay scaling). */\n frameWidth: number;\n frameHeight: number;\n /** Capture-v2 per-frame status vector (framing/lighting/blur/glare). */\n analysis: DocFrameAnalysis;\n}\n\n/** Artifacts: `keyframe` = the full-res still of the framed side (JPEG). */\nexport type DocArtifacts = CaptureArtifacts & { side?: DocSide };\n\nexport interface CaptureSessionDocEvents {\n statechange: { state: CaptureState };\n checks: DocChecksPayload;\n /** dcv1.* message KEY — hosts translate; never prose. Deduped. */\n guidance: { key: string };\n /** Capture-v2 AGGREGATED feedback (time-decay voted, min-duration held) —\n * the stage's message + reticle channel. Deduped on state key. */\n feedback: DocFeedbackState;\n artifacts: DocArtifacts;\n error: { kind: CaptureErrorKind; retryable: boolean };\n}\n\nexport interface CaptureSessionDocOptions {\n /** Host-owned <video playsInline muted>. */\n video: HTMLVideoElement;\n format?: DocFormat;\n /** Side label stamped on the artifacts event (recto→verso sequencing). */\n side?: DocSide;\n thresholds?: Partial<DocGateThresholds>;\n deviceClass?: DeviceClass;\n // ── seams (production defaults in parens) ─────────────────────────────────\n /** (navigator.mediaDevices.getUserMedia — environment camera) */\n getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;\n /** (vendored scanic detectQuad) */\n detect?: (image: ImageData) => Promise<DocQuad | null>;\n /** (canvas downscale of the live video to the 480px detect frame) */\n grabDetectFrame?: (video: HTMLVideoElement, targetWidth: number) => ImageData | null;\n /** (full-res canvas still → JPEG blob + preview; quad passed for telemetry) */\n captureStill?: (\n video: HTMLVideoElement,\n quad: DocQuad,\n detectSize: { width: number; height: number },\n ) => Promise<{ blob: Blob; previewDataUrl: string }>;\n /** (performance.now) */\n now?: () => number;\n /** (setTimeout/clearTimeout) — the cadence pacer. */\n setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimer?: (id: ReturnType<typeof setTimeout>) => void;\n}\n\ntype Handler<K extends keyof CaptureSessionDocEvents> = (\n payload: CaptureSessionDocEvents[K],\n) => void;\n\nexport class CaptureSessionDoc {\n private readonly opts: CaptureSessionDocOptions;\n private readonly gate: DocGateV1;\n private readonly cadenceMs: number;\n private readonly handlers = new Map<string, Set<(payload: never) => void>>();\n private stream: MediaStream | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private state: CaptureState = \"idle\";\n private lastGuidance: string | null = null;\n private attempt = 0;\n private capturing = false;\n private readonly feedback = new DocFeedbackAggregator();\n private lastFeedbackKey: string | null = null;\n\n constructor(opts: CaptureSessionDocOptions) {\n this.opts = opts;\n this.gate = new DocGateV1(opts.format ?? \"ID-1\", opts.thresholds);\n const deviceClass = opts.deviceClass ?? classifyDevice(probeDevice());\n this.cadenceMs = 1000 / DOC_CADENCE_HZ[deviceClass];\n }\n\n on<K extends keyof CaptureSessionDocEvents>(event: K, handler: Handler<K>): () => void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler as (payload: never) => void);\n return () => set?.delete(handler as (payload: never) => void);\n }\n\n private emit<K extends keyof CaptureSessionDocEvents>(\n event: K,\n payload: CaptureSessionDocEvents[K],\n ): void {\n for (const handler of [...(this.handlers.get(event) ?? [])]) {\n (handler as Handler<K>)(payload);\n }\n }\n\n async start(): Promise<void> {\n const attempt = ++this.attempt;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n this.setState(\"preflight\");\n try {\n const getUserMedia =\n this.opts.getUserMedia ??\n ((constraints: MediaStreamConstraints) =>\n navigator.mediaDevices.getUserMedia(constraints));\n this.setState(\"acquiring\");\n this.stream = await getUserMedia({\n audio: false,\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n });\n if (attempt !== this.attempt) {\n for (const track of this.stream.getTracks()) track.stop();\n return;\n }\n this.opts.video.srcObject = this.stream;\n await this.opts.video.play().catch(() => undefined);\n this.setState(\"positioning\");\n this.scheduleTick(attempt);\n } catch (err) {\n const kind = mapGetUserMediaError(err);\n this.setState(\"error\");\n this.emit(\"error\", { kind, retryable: true });\n }\n }\n\n /** Restart the gate for another side / a retake (same stream). */\n rearm(side?: DocSide): void {\n if (side !== undefined) this.opts.side = side;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n if (this.state === \"captured\") {\n this.setState(\"positioning\");\n this.scheduleTick(this.attempt);\n }\n }\n\n destroy(): void {\n this.attempt += 1;\n if (this.timer !== null) (this.opts.clearTimer ?? clearTimeout)(this.timer);\n this.timer = null;\n if (this.stream) for (const track of this.stream.getTracks()) track.stop();\n this.stream = null;\n this.opts.video.srcObject = null;\n }\n\n private setState(state: CaptureState): void {\n this.state = state;\n this.emit(\"statechange\", { state });\n }\n\n private scheduleTick(attempt: number): void {\n const setTimer = this.opts.setTimer ?? setTimeout;\n this.timer = setTimer(() => void this.tick(attempt), this.cadenceMs);\n }\n\n private async tick(attempt: number): Promise<void> {\n if (attempt !== this.attempt || this.state !== \"positioning\" || this.capturing) return;\n const now = this.opts.now ?? (() => performance.now());\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const detect =\n this.opts.detect ??\n ((this.opts.format ?? \"ID-1\") === \"ID-3\" ? mrzBandDetect : lazyScanicDetect);\n\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) {\n this.scheduleTick(attempt);\n return;\n }\n let quad: DocQuad | null = null;\n try {\n quad = await detect(frame);\n } catch {\n quad = null; // a detector hiccup is a no-doc tick, never a crash\n }\n if (attempt !== this.attempt) return;\n\n const crop = quad !== null ? cropQuadRegion(frame, quad) : frame;\n const atMs = now();\n const measures = {\n sharpness: sharpness(crop),\n exposure: exposure(crop),\n meanLuma: meanLuma(crop),\n specularFraction: specularFraction(crop),\n };\n const analysis = analyzeDocFrame({\n format: this.opts.format ?? \"ID-1\",\n quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n measures,\n ...(this.opts.thresholds !== undefined ? { thresholds: this.opts.thresholds } : {}),\n });\n const tick = this.gate.tick({\n timestampMs: atMs,\n quad,\n quality: measures,\n frameWidth: frame.width,\n frameHeight: frame.height,\n });\n this.emit(\"checks\", {\n engine: \"docv1\",\n p: tick.p,\n plausible: tick.plausible,\n stableTicks: tick.stableTicks,\n quad: tick.quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysis,\n });\n // Capture-v2 feedback: time-decay voted + min-duration held (anti-flicker).\n const feedbackState = this.feedback.tick(feedbackVoteOf(analysis), atMs);\n if (feedbackState.key !== this.lastFeedbackKey) {\n this.lastFeedbackKey = feedbackState.key;\n this.emit(\"feedback\", feedbackState);\n }\n if (tick.guidanceKey !== this.lastGuidance) {\n this.lastGuidance = tick.guidanceKey;\n this.emit(\"guidance\", { key: tick.guidanceKey });\n }\n\n if (tick.ready && quad !== null) {\n const processing = this.feedback.force(\"processing\", atMs);\n this.lastFeedbackKey = processing.key;\n this.emit(\"feedback\", processing);\n await this.fire(attempt, quad, {\n width: frame.width,\n height: frame.height,\n tickSharpness: measures.sharpness,\n });\n return;\n }\n this.scheduleTick(attempt);\n }\n\n /**\n * BEST-FRAME capture window (capture-v2): the first ready tick captures a\n * candidate immediately, then up to CAPTURE_EXTRA_CANDIDATES more stills are\n * taken at CAPTURE_CANDIDATE_SPACING_MS while the user holds — each scored\n * by the live detect-tick sharpness at its capture moment — and the SHARPEST\n * wins. Beats first-acceptable-frame capture without decoding stills.\n */\n private async fire(\n attempt: number,\n quad: DocQuad,\n detectSize: { width: number; height: number; tickSharpness: number },\n ): Promise<void> {\n this.capturing = true;\n const capture = this.opts.captureStill ?? defaultCaptureStill;\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const setTimer = this.opts.setTimer ?? setTimeout;\n const size = { width: detectSize.width, height: detectSize.height };\n try {\n let best = {\n still: await capture(this.opts.video, quad, size),\n sharpness: detectSize.tickSharpness,\n };\n for (let i = 0; i < CAPTURE_EXTRA_CANDIDATES; i++) {\n await new Promise<void>((resolve) => {\n setTimer(() => resolve(), CAPTURE_CANDIDATE_SPACING_MS);\n });\n if (attempt !== this.attempt) return;\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) continue;\n const candidateSharpness = sharpness(cropQuadRegion(frame, quad));\n if (candidateSharpness > best.sharpness * CAPTURE_IMPROVEMENT_MIN_RATIO) {\n const still = await capture(this.opts.video, quad, size);\n if (attempt !== this.attempt) return;\n best = { still, sharpness: candidateSharpness };\n }\n }\n if (attempt !== this.attempt) return;\n this.setState(\"captured\");\n this.emit(\"artifacts\", {\n keyframe: best.still.blob,\n previewDataUrl: best.still.previewDataUrl,\n ...(this.opts.side !== undefined ? { side: this.opts.side } : {}),\n });\n } catch {\n if (attempt !== this.attempt) return;\n // Still capture failed (canvas/encode) — rearm the gate and keep coaching.\n this.capturing = false;\n this.gate.reset();\n this.scheduleTick(attempt);\n }\n }\n}\n\n/** Best-frame window: 2 extra candidates, 260ms apart, ≥8% sharper to replace. */\nexport const CAPTURE_EXTRA_CANDIDATES = 2;\nexport const CAPTURE_CANDIDATE_SPACING_MS = 260;\nconst CAPTURE_IMPROVEMENT_MIN_RATIO = 1.08;\n\n/** Downscale the live video to the detect width (aspect preserved). */\nfunction defaultGrabDetectFrame(video: HTMLVideoElement, targetWidth: number): ImageData | null {\n const vw = video.videoWidth;\n const vh = video.videoHeight;\n if (vw === 0 || vh === 0) return null;\n const scale = Math.min(1, targetWidth / vw);\n const width = Math.round(vw * scale);\n const height = Math.round(vh * scale);\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (context === null) return null;\n context.drawImage(video, 0, 0, width, height);\n return context.getImageData(0, 0, width, height);\n}\n\n/**\n * Capture the FULL-RES frame (gate-timed, UNCROPPED — operator gate round 2):\n * client-side quad cropping cut the MRZ whenever the detected corners were\n * slightly off, and kyc-docs-v2 runs its OWN detector/warp on the server —\n * the client's job is TIMING (fire when framed/steady), not geometry. The\n * quad/detectSize params stay in the seam signature for telemetry/tests.\n */\nasync function defaultCaptureStill(\n video: HTMLVideoElement,\n _quad: DocQuad,\n _detectSize: { width: number; height: number },\n): Promise<{ blob: Blob; previewDataUrl: string }> {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, video.videoWidth);\n canvas.height = Math.max(1, video.videoHeight);\n const context = canvas.getContext(\"2d\");\n if (context === null) throw new Error(\"2d context unavailable\");\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error(\"toBlob failed\"))), \"image/jpeg\", 0.92);\n });\n return { blob, previewDataUrl: canvas.toDataURL(\"image/jpeg\", 0.85) };\n}\n\n/** Quad-bbox crop of the DETECT frame — the quality measures run on this. */\nfunction cropQuadRegion(frame: ImageData, quad: DocQuad): ImageData {\n const xs = [quad.topLeft.x, quad.topRight.x, quad.bottomRight.x, quad.bottomLeft.x];\n const ys = [quad.topLeft.y, quad.topRight.y, quad.bottomRight.y, quad.bottomLeft.y];\n const x1 = Math.max(0, Math.floor(Math.min(...xs)));\n const y1 = Math.max(0, Math.floor(Math.min(...ys)));\n const x2 = Math.min(frame.width, Math.ceil(Math.max(...xs)));\n const y2 = Math.min(frame.height, Math.ceil(Math.max(...ys)));\n const width = Math.max(1, x2 - x1);\n const height = Math.max(1, y2 - y1);\n const data = new Uint8ClampedArray(width * height * 4);\n for (let row = 0; row < height; row++) {\n const src = ((y1 + row) * frame.width + x1) * 4;\n data.set(frame.data.subarray(src, src + width * 4), row * width * 4);\n }\n return { data, width, height, colorSpace: frame.colorSpace } as ImageData;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCO,IAAM,oBAAiD,OAAO,OAAO;AAAA,EAC1E,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,eAAe;AACjB,CAAC;AAED,IAAM,iBAAiB;AAOhB,SAAS,cACd,OACA,YAAwC,CAAC,GACzB;AAChB,QAAM,IAAI,EAAE,GAAG,mBAAmB,GAAG,UAAU;AAC/C,QAAM,EAAE,MAAM,OAAO,OAAO,IAAI;AAChC,MAAI,QAAQ,MAAM,SAAS,GAAI,QAAO;AAGtC,QAAM,YAAY,IAAI,MAAe,MAAM,EAAE,KAAK,KAAK;AACvD,QAAM,UAAU,IAAI,aAAa,MAAM;AACvC,QAAM,OAAO,IAAI,WAAW,MAAM,EAAE,KAAK,KAAK;AAC9C,QAAM,OAAO,IAAI,WAAW,MAAM,EAAE,KAAK,EAAE;AAE3C,QAAM,cAAc,QAAQ;AAC5B,QAAM,UAAU,IAAI,MAAe,cAAc;AACjD,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,QAAI,QAAQ;AACZ,YAAQ,KAAK,KAAK;AAClB,UAAM,YAAY,IAAI,QAAQ;AAC9B,QAAI,eACF,SAAS,KAAK,SAAS,KAAK,KAC5B,SAAS,KAAK,YAAY,CAAC,KAAK,KAChC,SAAS,KAAK,YAAY,CAAC,KAAK;AAClC,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,IAAI,YAAY,IAAI;AAC1B,YAAM,OACJ,SAAS,KAAK,CAAC,KAAK,KAAK,SAAS,KAAK,IAAI,CAAC,KAAK,KAAK,SAAS,KAAK,IAAI,CAAC,KAAK;AAChF,UAAI,KAAK,IAAI,OAAO,YAAY,KAAK,EAAE,iBAAiB;AACtD,iBAAS;AACT,gBAAQ,KAAK,IAAI,iBAAiB,GAAG,KAAK,MAAM,IAAI,WAAW,CAAC,CAAC,IAAI;AACrE,YAAI,IAAK,KAAK,CAAC,EAAc,MAAK,CAAC,IAAI;AACvC,YAAI,IAAK,KAAK,CAAC,EAAc,MAAK,CAAC,IAAI;AAAA,MACzC;AACA,qBAAe;AAAA,IACjB;AACA,YAAQ,CAAC,IAAI,QAAQ;AACrB,UAAM,WAAW,QAAQ,OAAO,OAAO,EAAE,SAAS;AAClD,cAAU,CAAC,IACT,QAAQ,CAAC,KAAM,EAAE,mBACjB,YAAY,EAAE,uBACb,KAAK,CAAC,IAAgB,KAAK,CAAC,KAAgB,EAAE,iBAAiB;AAAA,EACpE;AAGA,MAAI,OAA6D;AACjE,MAAI,SAAS;AACb,MAAI,iBAAiB;AACrB,MAAI,UAAU;AACd,QAAM,QAAQ,CAAC,WAAmB;AAChC,QAAI,SAAS,EAAG;AAChB,UAAM,aAAa,SAAS,SAAS;AACrC,QACE,cAAc,EAAE,kBAAkB,UAClC,cAAc,EAAE,kBAAkB,UAClC,WAAW;AAAA,KACV,SAAS,QAAQ,UAAU,KAAK,OACjC;AACA,aAAO,EAAE,KAAK,QAAQ,QAAQ,MAAM,QAAQ;AAAA,IAC9C;AACA,aAAS;AACT,cAAU;AAAA,EACZ;AACA,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,QAAI,UAAU,CAAC,GAAG;AAChB,UAAI,SAAS,EAAG,UAAS;AAAA,eAChB,IAAI,iBAAiB,EAAE,eAAe;AAC7C,cAAM,cAAc;AACpB,iBAAS;AAAA,MACX;AACA,uBAAiB;AACjB,iBAAW;AAAA,IACb;AAAA,EACF;AACA,QAAM,cAAc;AACpB,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,OAAO;AAGb,MAAI,KAAK;AACT,MAAI,KAAK;AACT,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,WAAS,IAAI,KAAK,KAAK,KAAK,KAAK,QAAQ,KAAK;AAC5C,QAAI,CAAC,UAAU,CAAC,EAAG;AACnB,QAAK,KAAK,CAAC,IAAe,GAAI,MAAK,KAAK,CAAC;AACzC,QAAK,KAAK,CAAC,IAAe,GAAI,MAAK,KAAK,CAAC;AACzC,kBAAc,QAAQ,CAAC;AACvB,mBAAe;AAAA,EACjB;AACA,MAAI,KAAK,KAAK,EAAE,iBAAiB,MAAO,QAAO;AAC/C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,KAAK;AAAA,IACR,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK,SAAS,KAAK,MAAM;AAAA,IACjC,SAAS,cAAc,IAAI,aAAa,cAAc;AAAA,EACxD;AACF;;;ACxGO,IAAM,wBAAwB,OAAO,OAAO;AAAA;AAAA,EAEjD,eAAe;AAAA;AAAA,EAEf,qBAAqB;AAAA;AAAA,EAErB,yBAAyB;AAC3B,CAAC;AAGM,SAAS,iBAAiB,MAAyB;AACxD,QAAM,EAAE,MAAM,OAAO,OAAO,IAAI;AAChC,QAAM,SAAS,QAAQ;AACvB,MAAI,WAAW,EAAG,QAAO;AACzB,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI;AAEd,UAAM,OACJ,SAAS,KAAK,CAAC,KAAK,KAAK,SAAS,KAAK,IAAI,CAAC,KAAK,KAAK,SAAS,KAAK,IAAI,CAAC,KAAK;AAChF,QAAI,QAAQ,IAAK;AAAA,EACnB;AACA,SAAO,MAAM;AACf;AAGO,SAAS,cAAc,MAAuB;AACnD,QAAM,MAAM,CAAC,GAA6B,MACxC,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACjC,QAAM,MAAM,IAAI,KAAK,SAAS,KAAK,QAAQ;AAC3C,QAAM,SAAS,IAAI,KAAK,YAAY,KAAK,WAAW;AACpD,QAAM,OAAO,IAAI,KAAK,SAAS,KAAK,UAAU;AAC9C,QAAM,QAAQ,IAAI,KAAK,UAAU,KAAK,WAAW;AACjD,QAAM,IAAI,KAAK,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC;AACtE,QAAM,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,CAAC;AACtE,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAOO,SAAS,gBAAgB,OAOX;AACnB,QAAM,IAAI,EAAE,GAAG,sBAAsB,GAAG,MAAM,WAAW;AACzD,QAAM,IAAI;AACV,QAAM,EAAE,MAAM,YAAY,aAAa,SAAS,IAAI;AACpD,QAAM,WAAW,MAAM,WAAW;AAElC,MAAI,UAAyB;AAC7B,MAAI,SAAS,MAAM;AACjB,cAAU;AAAA,EACZ,WAAW,UAAU;AACnB,UAAM,SAAS,WAAW,IAAI;AAC9B,UAAM,YAAY,aAAa,IAAI,OAAO,QAAQ,aAAa;AAC/D,UAAM,SAAS,OAAO,SAAS,IAAI,OAAO,QAAQ,OAAO,SAAS;AAClE,QAAI,YAAY,EAAE,wBAAyB,WAAU;AAAA,aAC5C,YAAY,MAAM,YAAY,aAAa,EAAE,mBAAmB,GAAG;AAC1E,gBAAU;AAAA,IACZ,WAAW,SAAS,EAAE,uBAAuB,SAAS,EAAE,qBAAqB;AAC3E,gBAAU;AAAA,IACZ;AAAA,EACF,OAAO;AACL,UAAM,WAAW,aAAa,MAAM,YAAY,WAAW;AAC3D,UAAM,OAAO,WAAW,MAAM,MAAM;AACpC,UAAM,SAAS,WAAW,IAAI;AAC9B,UAAM,SACJ,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,CAAC;AAC9F,QAAI,WAAW,EAAE,cAAe,WAAU;AAAA,aACjC,WAAW,EAAE,cAAe,WAAU;AAAA,aACtC,YAAY,MAAM,YAAY,aAAa,EAAE,mBAAmB,GAAG;AAC1E,gBAAU;AAAA,IACZ,WAAW,KAAK,IAAI,SAAS,KAAK,MAAM,IAAI,KAAK,UAAW,WAAU;AAAA,aAC7D,cAAc,IAAI,IAAI,EAAE,oBAAqB,WAAU;AAAA,EAClE;AAEA,QAAM,WACJ,SAAS,YAAY,EAAE,iBACnB,OACA,SAAS,YAAY,cAAc,CAAC,IAAI,cAAc,CAAC,KAAK,IAC1D,aACA;AACR,QAAM,OAAmB,SAAS,aAAa,EAAE,kBAAkB,OAAO;AAC1E,QAAM,QACJ,SAAS,oBAAoB,EAAE,0BAA0B,UAAU;AAErE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,YAAY,QAAQ,aAAa,QAAQ,SAAS,QAAQ,UAAU;AAAA,EAClF;AACF;AAEA,SAAS,YACP,MACA,YACA,aACA,QACS;AACT,QAAM,MAAM,CAAC,KAAK,SAAS,KAAK,UAAU,KAAK,aAAa,KAAK,UAAU;AAC3E,SAAO,IAAI;AAAA,IACT,CAAC,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,UAAU,EAAE,KAAK,cAAc;AAAA,EAC9F;AACF;;;AC/HO,IAAM,sBACX,OAAO,OAAO;AAAA,EACZ,SAAS,EAAE,KAAK,WAAW,SAAS,aAAa,eAAe,IAAI;AAAA,EACpE,SAAS,EAAE,KAAK,WAAW,SAAS,YAAY,eAAe,KAAK;AAAA,EACpE,WAAW,EAAE,KAAK,aAAa,SAAS,YAAY,eAAe,KAAK;AAAA,EACxE,cAAc,EAAE,KAAK,gBAAgB,SAAS,YAAY,eAAe,KAAK;AAAA,EAC9E,aAAa,EAAE,KAAK,eAAe,SAAS,YAAY,eAAe,KAAK;AAAA,EAC5E,QAAQ,EAAE,KAAK,UAAU,SAAS,YAAY,eAAe,KAAK;AAAA,EAClE,UAAU,EAAE,KAAK,YAAY,SAAS,YAAY,eAAe,KAAK;AAAA,EACtE,YAAY,EAAE,KAAK,cAAc,SAAS,YAAY,eAAe,KAAK;AAAA,EAC1E,QAAQ,EAAE,KAAK,UAAU,SAAS,YAAY,eAAe,KAAK;AAAA,EAClE,OAAO,EAAE,KAAK,SAAS,SAAS,YAAY,eAAe,KAAK;AAAA,EAChE,YAAY,EAAE,KAAK,cAAc,SAAS,WAAW,eAAe,IAAI;AAAA,EACxE,YAAY,EAAE,KAAK,cAAc,SAAS,cAAc,eAAe,IAAI;AAAA,EAC3E,eAAe,EAAE,KAAK,iBAAiB,SAAS,QAAQ,eAAe,KAAK;AAAA,EAC5E,UAAU,EAAE,KAAK,YAAY,SAAS,QAAQ,eAAe,IAAI;AACnE,CAAC;AAGI,SAAS,eAAe,UAA4C;AACzE,MAAI,SAAS,YAAY,cAAe,QAAO;AAC/C,MAAI,SAAS,YAAY,UAAW,QAAO;AAC3C,MAAI,SAAS,YAAY,YAAa,QAAO;AAC7C,MAAI,SAAS,YAAY,eAAgB,QAAO;AAChD,MAAI,SAAS,YAAY,cAAe,QAAO;AAC/C,MAAI,SAAS,YAAY,SAAU,QAAO;AAC1C,MAAI,SAAS,aAAa,WAAY,QAAO;AAC7C,MAAI,SAAS,aAAa,aAAc,QAAO;AAC/C,MAAI,SAAS,UAAU,QAAS,QAAO;AACvC,MAAI,SAAS,SAAS,SAAU,QAAO;AACvC,SAAO;AACT;AAQO,IAAM,wBAAN,MAA4B;AAAA,EAKjC,YACmB,WAAW,MAEX,iBAAiB,MAClC;AAHiB;AAEA;AAPnB,SAAQ,QAAgB,CAAC;AACzB,SAAQ,UAA4B,oBAAoB;AACxD,SAAQ,iBAAgC;AAAA,EAMrC;AAAA,EAEH,QAAc;AACZ,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU,oBAAoB;AACnC,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA,EAGA,MAAM,KAA2E,OAAiC;AAChH,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU,oBAAoB,GAAG;AACtC,SAAK,iBAAiB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAK,MAAsB,OAAiC;AAC1D,QAAI,KAAK,mBAAmB,KAAM,MAAK,iBAAiB;AACxD,SAAK,MAAM,KAAK,EAAE,KAAK,MAAM,MAAM,MAAM,CAAC;AAC1C,SAAK,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,QAAQ,EAAE,QAAQ,KAAK,QAAQ;AAGrE,QAAI,QAAQ,KAAK,iBAAiB,KAAK,QAAQ,cAAe,QAAO,KAAK;AAE1E,UAAM,SAAS,oBAAI,IAA4B;AAC/C,eAAW,KAAK,KAAK,OAAO;AAC1B,YAAM,cAAc,QAAQ,EAAE,QAAQ;AACtC,YAAM,SAAS,KAAK,kBAAkB;AACtC,aAAO,IAAI,EAAE,MAAM,OAAO,IAAI,EAAE,GAAG,KAAK,KAAK,MAAM;AAAA,IACrD;AACA,QAAI,SAAyB,KAAK,QAAQ;AAC1C,QAAI,OAAO;AACX,eAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,UAAI,QAAQ,MAAM;AAChB,eAAO;AACP,iBAAS;AAAA,MACX;AAAA,IACF;AACA,QAAI,WAAW,KAAK,QAAQ,KAAK;AAC/B,WAAK,UAAU,oBAAoB,MAAM;AACzC,WAAK,iBAAiB;AAAA,IACxB;AACA,WAAO,KAAK;AAAA,EACd;AACF;;;AC/FA,IAAI,eAAuE;AAC3E,eAAe,iBAAiB,OAA2C;AACzE,MAAI,iBAAiB,MAAM;AACzB,UAAM,UAAU,MAAM,OAAO,gCAAmB;AAChD,mBAAe,QAAQ;AAAA,EACzB;AACA,SAAO,aAAa,KAAK;AAC3B;AAQA,eAAe,cAAc,OAA2C;AACtE,QAAM,OAAO,cAAc,KAAK;AAChC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO;AAAA,IACL,SAAS,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE;AAAA,IAChC,UAAU,EAAE,GAAG,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,EAAE;AAAA,IAC9C,aAAa,EAAE,GAAG,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,IAAI,KAAK,OAAO;AAAA,IAC/D,YAAY,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,OAAO;AAAA,EACnD;AACF;AAOO,SAAS,kBAAwB;AACtC,MAAI,iBAAiB,KAAM;AAC3B,OAAK,OAAO,gCAAmB,EAC5B,KAAK,CAAC,YAAY;AACjB,mBAAe,QAAQ;AAAA,EACzB,CAAC,EACA,MAAM,MAAM,MAAS;AAC1B;AAIO,IAAM,iBAAwD,OAAO,OAAO;AAAA,EACjF,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AACR,CAAC;AA+DM,IAAM,oBAAN,MAAwB;AAAA,EAc7B,YAAY,MAAgC;AAV5C,SAAiB,WAAW,oBAAI,IAA2C;AAC3E,SAAQ,SAA6B;AACrC,SAAQ,QAA8C;AACtD,SAAQ,QAAsB;AAC9B,SAAQ,eAA8B;AACtC,SAAQ,UAAU;AAClB,SAAQ,YAAY;AACpB,SAAiB,WAAW,IAAI,sBAAsB;AACtD,SAAQ,kBAAiC;AAGvC,SAAK,OAAO;AACZ,SAAK,OAAO,IAAI,UAAU,KAAK,UAAU,QAAQ,KAAK,UAAU;AAChE,UAAM,cAAc,KAAK,eAAe,eAAe,YAAY,CAAC;AACpE,SAAK,YAAY,MAAO,eAAe,WAAW;AAAA,EACpD;AAAA,EAEA,GAA4C,OAAU,SAAiC;AACrF,QAAI,MAAM,KAAK,SAAS,IAAI,KAAK;AACjC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,SAAS,IAAI,OAAO,GAAG;AAAA,IAC9B;AACA,QAAI,IAAI,OAAmC;AAC3C,WAAO,MAAM,KAAK,OAAO,OAAmC;AAAA,EAC9D;AAAA,EAEQ,KACN,OACA,SACM;AACN,eAAW,WAAW,CAAC,GAAI,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC,CAAE,GAAG;AAC3D,MAAC,QAAuB,OAAO;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,UAAU,EAAE,KAAK;AACvB,SAAK,KAAK,MAAM;AAChB,SAAK,SAAS,MAAM;AACpB,SAAK,kBAAkB;AACvB,SAAK,eAAe;AACpB,SAAK,YAAY;AACjB,SAAK,SAAS,WAAW;AACzB,QAAI;AACF,YAAM,eACJ,KAAK,KAAK,iBACT,CAAC,gBACA,UAAU,aAAa,aAAa,WAAW;AACnD,WAAK,SAAS,WAAW;AACzB,WAAK,SAAS,MAAM,aAAa;AAAA,QAC/B,OAAO;AAAA,QACP,OAAO;AAAA,UACL,YAAY,EAAE,OAAO,cAAc;AAAA,UACnC,OAAO,EAAE,OAAO,KAAK;AAAA,UACrB,QAAQ,EAAE,OAAO,KAAK;AAAA,QACxB;AAAA,MACF,CAAC;AACD,UAAI,YAAY,KAAK,SAAS;AAC5B,mBAAW,SAAS,KAAK,OAAO,UAAU,EAAG,OAAM,KAAK;AACxD;AAAA,MACF;AACA,WAAK,KAAK,MAAM,YAAY,KAAK;AACjC,YAAM,KAAK,KAAK,MAAM,KAAK,EAAE,MAAM,MAAM,MAAS;AAClD,WAAK,SAAS,aAAa;AAC3B,WAAK,aAAa,OAAO;AAAA,IAC3B,SAAS,KAAK;AACZ,YAAM,OAAO,qBAAqB,GAAG;AACrC,WAAK,SAAS,OAAO;AACrB,WAAK,KAAK,SAAS,EAAE,MAAM,WAAW,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,MAAsB;AAC1B,QAAI,SAAS,OAAW,MAAK,KAAK,OAAO;AACzC,SAAK,KAAK,MAAM;AAChB,SAAK,SAAS,MAAM;AACpB,SAAK,kBAAkB;AACvB,SAAK,eAAe;AACpB,SAAK,YAAY;AACjB,QAAI,KAAK,UAAU,YAAY;AAC7B,WAAK,SAAS,aAAa;AAC3B,WAAK,aAAa,KAAK,OAAO;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,WAAW;AAChB,QAAI,KAAK,UAAU,KAAM,EAAC,KAAK,KAAK,cAAc,cAAc,KAAK,KAAK;AAC1E,SAAK,QAAQ;AACb,QAAI,KAAK,OAAQ,YAAW,SAAS,KAAK,OAAO,UAAU,EAAG,OAAM,KAAK;AACzE,SAAK,SAAS;AACd,SAAK,KAAK,MAAM,YAAY;AAAA,EAC9B;AAAA,EAEQ,SAAS,OAA2B;AAC1C,SAAK,QAAQ;AACb,SAAK,KAAK,eAAe,EAAE,MAAM,CAAC;AAAA,EACpC;AAAA,EAEQ,aAAa,SAAuB;AAC1C,UAAM,WAAW,KAAK,KAAK,YAAY;AACvC,SAAK,QAAQ,SAAS,MAAM,KAAK,KAAK,KAAK,OAAO,GAAG,KAAK,SAAS;AAAA,EACrE;AAAA,EAEA,MAAc,KAAK,SAAgC;AACjD,QAAI,YAAY,KAAK,WAAW,KAAK,UAAU,iBAAiB,KAAK,UAAW;AAChF,UAAM,MAAM,KAAK,KAAK,QAAQ,MAAM,YAAY,IAAI;AACpD,UAAM,OAAO,KAAK,KAAK,mBAAmB;AAC1C,UAAM,SACJ,KAAK,KAAK,YACR,KAAK,KAAK,UAAU,YAAY,SAAS,gBAAgB;AAE7D,UAAM,QAAQ,KAAK,KAAK,KAAK,OAAO,gBAAgB;AACpD,QAAI,UAAU,MAAM;AAClB,WAAK,aAAa,OAAO;AACzB;AAAA,IACF;AACA,QAAI,OAAuB;AAC3B,QAAI;AACF,aAAO,MAAM,OAAO,KAAK;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI,YAAY,KAAK,QAAS;AAE9B,UAAM,OAAO,SAAS,OAAO,eAAe,OAAO,IAAI,IAAI;AAC3D,UAAM,OAAO,IAAI;AACjB,UAAM,WAAW;AAAA,MACf,WAAW,UAAU,IAAI;AAAA,MACzB,UAAU,SAAS,IAAI;AAAA,MACvB,UAAU,SAAS,IAAI;AAAA,MACvB,kBAAkB,iBAAiB,IAAI;AAAA,IACzC;AACA,UAAM,WAAW,gBAAgB;AAAA,MAC/B,QAAQ,KAAK,KAAK,UAAU;AAAA,MAC5B;AAAA,MACA,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,GAAI,KAAK,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,IACnF,CAAC;AACD,UAAM,OAAO,KAAK,KAAK,KAAK;AAAA,MAC1B,aAAa;AAAA,MACb;AAAA,MACA,SAAS;AAAA,MACT,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,IACrB,CAAC;AACD,SAAK,KAAK,UAAU;AAAA,MAClB,QAAQ;AAAA,MACR,GAAG,KAAK;AAAA,MACR,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,MAAM,KAAK;AAAA,MACX,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,KAAK,SAAS,KAAK,eAAe,QAAQ,GAAG,IAAI;AACvE,QAAI,cAAc,QAAQ,KAAK,iBAAiB;AAC9C,WAAK,kBAAkB,cAAc;AACrC,WAAK,KAAK,YAAY,aAAa;AAAA,IACrC;AACA,QAAI,KAAK,gBAAgB,KAAK,cAAc;AAC1C,WAAK,eAAe,KAAK;AACzB,WAAK,KAAK,YAAY,EAAE,KAAK,KAAK,YAAY,CAAC;AAAA,IACjD;AAEA,QAAI,KAAK,SAAS,SAAS,MAAM;AAC/B,YAAM,aAAa,KAAK,SAAS,MAAM,cAAc,IAAI;AACzD,WAAK,kBAAkB,WAAW;AAClC,WAAK,KAAK,YAAY,UAAU;AAChC,YAAM,KAAK,KAAK,SAAS,MAAM;AAAA,QAC7B,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM;AAAA,QACd,eAAe,SAAS;AAAA,MAC1B,CAAC;AACD;AAAA,IACF;AACA,SAAK,aAAa,OAAO;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,KACZ,SACA,MACA,YACe;AACf,SAAK,YAAY;AACjB,UAAM,UAAU,KAAK,KAAK,gBAAgB;AAC1C,UAAM,OAAO,KAAK,KAAK,mBAAmB;AAC1C,UAAM,WAAW,KAAK,KAAK,YAAY;AACvC,UAAM,OAAO,EAAE,OAAO,WAAW,OAAO,QAAQ,WAAW,OAAO;AAClE,QAAI;AACF,UAAI,OAAO;AAAA,QACT,OAAO,MAAM,QAAQ,KAAK,KAAK,OAAO,MAAM,IAAI;AAAA,QAChD,WAAW,WAAW;AAAA,MACxB;AACA,eAAS,IAAI,GAAG,IAAI,0BAA0B,KAAK;AACjD,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,mBAAS,MAAM,QAAQ,GAAG,4BAA4B;AAAA,QACxD,CAAC;AACD,YAAI,YAAY,KAAK,QAAS;AAC9B,cAAM,QAAQ,KAAK,KAAK,KAAK,OAAO,gBAAgB;AACpD,YAAI,UAAU,KAAM;AACpB,cAAM,qBAAqB,UAAU,eAAe,OAAO,IAAI,CAAC;AAChE,YAAI,qBAAqB,KAAK,YAAY,+BAA+B;AACvE,gBAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,OAAO,MAAM,IAAI;AACvD,cAAI,YAAY,KAAK,QAAS;AAC9B,iBAAO,EAAE,OAAO,WAAW,mBAAmB;AAAA,QAChD;AAAA,MACF;AACA,UAAI,YAAY,KAAK,QAAS;AAC9B,WAAK,SAAS,UAAU;AACxB,WAAK,KAAK,aAAa;AAAA,QACrB,UAAU,KAAK,MAAM;AAAA,QACrB,gBAAgB,KAAK,MAAM;AAAA,QAC3B,GAAI,KAAK,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,KAAK,IAAI,CAAC;AAAA,MACjE,CAAC;AAAA,IACH,QAAQ;AACN,UAAI,YAAY,KAAK,QAAS;AAE9B,WAAK,YAAY;AACjB,WAAK,KAAK,MAAM;AAChB,WAAK,aAAa,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AAGO,IAAM,2BAA2B;AACjC,IAAM,+BAA+B;AAC5C,IAAM,gCAAgC;AAGtC,SAAS,uBAAuB,OAAyB,aAAuC;AAC9F,QAAM,KAAK,MAAM;AACjB,QAAM,KAAK,MAAM;AACjB,MAAI,OAAO,KAAK,OAAO,EAAG,QAAO;AACjC,QAAM,QAAQ,KAAK,IAAI,GAAG,cAAc,EAAE;AAC1C,QAAM,QAAQ,KAAK,MAAM,KAAK,KAAK;AACnC,QAAM,SAAS,KAAK,MAAM,KAAK,KAAK;AACpC,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,UAAU,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;AACpE,MAAI,YAAY,KAAM,QAAO;AAC7B,UAAQ,UAAU,OAAO,GAAG,GAAG,OAAO,MAAM;AAC5C,SAAO,QAAQ,aAAa,GAAG,GAAG,OAAO,MAAM;AACjD;AASA,eAAe,oBACb,OACA,OACA,aACiD;AACjD,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ,KAAK,IAAI,GAAG,MAAM,UAAU;AAC3C,SAAO,SAAS,KAAK,IAAI,GAAG,MAAM,WAAW;AAC7C,QAAM,UAAU,OAAO,WAAW,IAAI;AACtC,MAAI,YAAY,KAAM,OAAM,IAAI,MAAM,wBAAwB;AAC9D,UAAQ,UAAU,OAAO,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AAC1D,QAAM,OAAO,MAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AACxD,WAAO,OAAO,CAAC,MAAO,IAAI,QAAQ,CAAC,IAAI,OAAO,IAAI,MAAM,eAAe,CAAC,GAAI,cAAc,IAAI;AAAA,EAChG,CAAC;AACD,SAAO,EAAE,MAAM,gBAAgB,OAAO,UAAU,cAAc,IAAI,EAAE;AACtE;AAGA,SAAS,eAAe,OAAkB,MAA0B;AAClE,QAAM,KAAK,CAAC,KAAK,QAAQ,GAAG,KAAK,SAAS,GAAG,KAAK,YAAY,GAAG,KAAK,WAAW,CAAC;AAClF,QAAM,KAAK,CAAC,KAAK,QAAQ,GAAG,KAAK,SAAS,GAAG,KAAK,YAAY,GAAG,KAAK,WAAW,CAAC;AAClF,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAClD,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAClD,QAAM,KAAK,KAAK,IAAI,MAAM,OAAO,KAAK,KAAK,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAC3D,QAAM,KAAK,KAAK,IAAI,MAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAC5D,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,EAAE;AACjC,QAAM,SAAS,KAAK,IAAI,GAAG,KAAK,EAAE;AAClC,QAAM,OAAO,IAAI,kBAAkB,QAAQ,SAAS,CAAC;AACrD,WAAS,MAAM,GAAG,MAAM,QAAQ,OAAO;AACrC,UAAM,QAAQ,KAAK,OAAO,MAAM,QAAQ,MAAM;AAC9C,SAAK,IAAI,MAAM,KAAK,SAAS,KAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,QAAQ,CAAC;AAAA,EACrE;AACA,SAAO,EAAE,MAAM,OAAO,QAAQ,YAAY,MAAM,WAAW;AAC7D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/doc/mrz-band.ts","../../src/core/doc/frame-analysis.ts","../../src/core/doc/feedback-aggregator.ts","../../src/core/doc/capture-session-doc.ts"],"sourcesContent":["// MRZ-band detector (Task 37 round 3) — the passport path's detection signal.\n//\n// WHY: edge/contour detection (scanic) fails on passport data pages — an open\n// booklet's page edges are white-on-white, so quads lock late or onto the\n// wrong rectangle (operator field finding, twice). The industry-standard\n// approach (Scanbot/Regula/Dynamsoft; PyImageSearch's classic blackhat\n// pipeline; YOLO-MRZ) is to detect the MRZ ITSELF: two 44-char lines of\n// high-contrast OCR-B strokes — the one feature of a passport that is\n// reliably detectable in any framing. This is the pure-JS analog of the\n// blackhat+gradient pipeline: per-row horizontal-stroke density with a\n// column-coverage requirement, then the tallest qualifying row run.\n//\n// Deterministic, allocation-light, no wasm: ~2ms on a 480px frame in node —\n// cheap enough to run at full doc cadence on LOW devices.\n\nexport interface MrzBand {\n x: number;\n y: number;\n width: number;\n height: number;\n /** Mean qualifying-row stroke density (diagnostics/telemetry). */\n density: number;\n}\n\nexport interface MrzBandThresholds {\n /** Luma step that counts as a text stroke edge. */\n EDGE_LUMA_DELTA: number;\n /** Min fraction of a row's pixels that are stroke edges to qualify. */\n MIN_ROW_DENSITY: number;\n /** Row must have stroke edges spread across ≥ this fraction of 12 columns. */\n MIN_COLUMN_COVERAGE: number;\n /** Band width (stroke x-extent) as a fraction of frame width. */\n MIN_WIDTH_FRAC: number;\n /** Band height bounds as fractions of frame height (2–3 OCR-B lines). */\n MIN_HEIGHT_FRAC: number;\n MAX_HEIGHT_FRAC: number;\n /** Rows this close together merge into one band (inter-line gap). */\n ROW_MERGE_GAP: number;\n}\n\n/** Frozen defaults (goldens in test/doc/mrz-band.test.ts — retune together). */\nexport const MRZ_BAND_DEFAULTS: Readonly<MrzBandThresholds> = Object.freeze({\n EDGE_LUMA_DELTA: 40,\n MIN_ROW_DENSITY: 0.08,\n MIN_COLUMN_COVERAGE: 0.6,\n MIN_WIDTH_FRAC: 0.55,\n MIN_HEIGHT_FRAC: 0.02,\n MAX_HEIGHT_FRAC: 0.16,\n ROW_MERGE_GAP: 18,\n});\n\nconst COLUMN_BUCKETS = 12;\n\n/**\n * Detect the MRZ stroke band on a (≤480px) grayscale-able frame. Returns the\n * tight band rect in frame pixels, or null when no qualifying band exists\n * (no MRZ in view, or only a partial/narrow slice of one).\n */\nexport function detectMrzBand(\n frame: ImageData,\n overrides: Partial<MrzBandThresholds> = {},\n): MrzBand | null {\n const t = { ...MRZ_BAND_DEFAULTS, ...overrides };\n const { data, width, height } = frame;\n if (width < 32 || height < 32) return null;\n\n // Per-row: stroke-edge count + column coverage + x-extent.\n const qualifies = new Array<boolean>(height).fill(false);\n const density = new Float32Array(height);\n const minX = new Int32Array(height).fill(width);\n const maxX = new Int32Array(height).fill(-1);\n\n const bucketWidth = width / COLUMN_BUCKETS;\n const buckets = new Array<boolean>(COLUMN_BUCKETS);\n for (let y = 0; y < height; y++) {\n let edges = 0;\n buckets.fill(false);\n const rowOffset = y * width * 4;\n let previousLuma =\n 0.299 * (data[rowOffset] ?? 0) +\n 0.587 * (data[rowOffset + 1] ?? 0) +\n 0.114 * (data[rowOffset + 2] ?? 0);\n for (let x = 1; x < width; x++) {\n const i = rowOffset + x * 4;\n const luma =\n 0.299 * (data[i] ?? 0) + 0.587 * (data[i + 1] ?? 0) + 0.114 * (data[i + 2] ?? 0);\n if (Math.abs(luma - previousLuma) >= t.EDGE_LUMA_DELTA) {\n edges += 1;\n buckets[Math.min(COLUMN_BUCKETS - 1, Math.floor(x / bucketWidth))] = true;\n if (x < (minX[y] as number)) minX[y] = x;\n if (x > (maxX[y] as number)) maxX[y] = x;\n }\n previousLuma = luma;\n }\n density[y] = edges / width;\n const coverage = buckets.filter(Boolean).length / COLUMN_BUCKETS;\n qualifies[y] =\n density[y]! >= t.MIN_ROW_DENSITY &&\n coverage >= t.MIN_COLUMN_COVERAGE &&\n (maxX[y] as number) - (minX[y] as number) >= t.MIN_WIDTH_FRAC * width;\n }\n\n // Merge qualifying rows across the inter-line gap; keep the best band.\n let best: { top: number; bottom: number; rows: number } | null = null;\n let runTop = -1;\n let lastQualifying = -1;\n let runRows = 0;\n const flush = (bottom: number) => {\n if (runTop < 0) return;\n const bandHeight = bottom - runTop + 1;\n if (\n bandHeight >= t.MIN_HEIGHT_FRAC * height &&\n bandHeight <= t.MAX_HEIGHT_FRAC * height &&\n runRows >= 4 && // at least a few real stroke rows, not a lone edge\n (best === null || runRows > best.rows)\n ) {\n best = { top: runTop, bottom, rows: runRows };\n }\n runTop = -1;\n runRows = 0;\n };\n for (let y = 0; y < height; y++) {\n if (qualifies[y]) {\n if (runTop < 0) runTop = y;\n else if (y - lastQualifying > t.ROW_MERGE_GAP) {\n flush(lastQualifying);\n runTop = y;\n }\n lastQualifying = y;\n runRows += 1;\n }\n }\n flush(lastQualifying);\n if (best === null) return null;\n const band = best as { top: number; bottom: number; rows: number };\n\n // Tight x-extent + mean density over the band's qualifying rows.\n let x1 = width;\n let x2 = -1;\n let densitySum = 0;\n let densityRows = 0;\n for (let y = band.top; y <= band.bottom; y++) {\n if (!qualifies[y]) continue;\n if ((minX[y] as number) < x1) x1 = minX[y] as number;\n if ((maxX[y] as number) > x2) x2 = maxX[y] as number;\n densitySum += density[y] as number;\n densityRows += 1;\n }\n if (x2 - x1 < t.MIN_WIDTH_FRAC * width) return null;\n return {\n x: x1,\n y: band.top,\n width: x2 - x1,\n height: band.bottom - band.top + 1,\n density: densityRows > 0 ? densitySum / densityRows : 0,\n };\n}\n","// Doc frame analysis (capture v2, Task-37 round 4) — the BlinkID-inspired\n// PER-FRAME status vector: independent framing / lighting / blur / glare /\n// tilt verdicts instead of a single collapsed guidance key. Pure and\n// deterministic: inputs are the detection (quad or MRZ band rect-as-quad),\n// the doc-region crop's quality measures, and static thresholds — no clocks,\n// no I/O. The FeedbackAggregator votes over these statuses; DocGateV1's\n// integrator/stability logic consumes `framingOk`/`qualityOk`.\nimport { EXPOSURE_BAND } from \"../v3/frame-quality\";\nimport { aspectBand, type DocFormat } from \"./doc-catalog\";\nimport {\n DOC_GATE_V1_DEFAULTS,\n type DocGateThresholds,\n type DocQuad,\n quadAreaFrac,\n quadBounds,\n} from \"./doc-gate-v1\";\n\nexport type FramingStatus =\n | \"no_document\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"ok\";\n\nexport type LightingStatus = \"too_dark\" | \"too_bright\" | \"ok\";\nexport type BlurStatus = \"blurry\" | \"ok\";\nexport type GlareStatus = \"glare\" | \"ok\";\n\n/** The per-frame analysis vector (every axis judged independently). */\nexport interface DocFrameAnalysis {\n framing: FramingStatus;\n lighting: LightingStatus;\n blur: BlurStatus;\n glare: GlareStatus;\n /** All-clear: every axis ok — the gate's integrator/stability input. */\n acceptable: boolean;\n}\n\nexport interface DocFrameMeasures {\n /** frame-quality sharpness on the doc-region crop (0..1). */\n sharpness: number;\n /** frame-quality exposure on the doc-region crop (0..1). */\n exposure: number;\n /** Mean crop luma (0..255) — picks too_dark vs too_bright. */\n meanLuma: number;\n /** Fraction of crop pixels at/above the specular ceiling (0..1). */\n specularFraction: number;\n}\n\n/** Analysis thresholds beyond the gate's — frozen defaults (round 4). */\nexport const DOC_ANALYSIS_DEFAULTS = Object.freeze({\n /** Doc area fraction above which the camera is too close (capture would crop). */\n MAX_AREA_FRAC: 0.92,\n /** Opposite-side length ratio beyond which the view is too oblique. */\n TILT_MAX_SIDE_RATIO: 1.3,\n /** Specular-pixel fraction at/above which the frame reads as glare. */\n GLARE_MAX_SPECULAR_FRAC: 0.025,\n});\n\n/** Fraction of crop pixels with luma ≥ 252 — cheap specular-highlight measure. */\nexport function specularFraction(crop: ImageData): number {\n const { data, width, height } = crop;\n const pixels = width * height;\n if (pixels === 0) return 0;\n let hot = 0;\n for (let i = 0; i < pixels; i++) {\n const o = i * 4;\n // Rec.601 luma, integer-ish — a specular blowout saturates all channels.\n const luma =\n 0.299 * (data[o] ?? 0) + 0.587 * (data[o + 1] ?? 0) + 0.114 * (data[o + 2] ?? 0);\n if (luma >= 252) hot++;\n }\n return hot / pixels;\n}\n\n/** Opposite-side length ratio of the quad (1 = fronto-parallel). */\nexport function quadTiltRatio(quad: DocQuad): number {\n const len = (a: { x: number; y: number }, b: { x: number; y: number }) =>\n Math.hypot(b.x - a.x, b.y - a.y);\n const top = len(quad.topLeft, quad.topRight);\n const bottom = len(quad.bottomLeft, quad.bottomRight);\n const left = len(quad.topLeft, quad.bottomLeft);\n const right = len(quad.topRight, quad.bottomRight);\n const h = Math.max(top, bottom) / Math.max(1e-6, Math.min(top, bottom));\n const v = Math.max(left, right) / Math.max(1e-6, Math.min(left, right));\n return Math.max(h, v);\n}\n\n/**\n * Judge one frame. `detectionMode` mirrors the gate: ID-1 quads carry\n * area/aspect/tilt semantics; ID-3 MRZ bands carry width-fraction semantics\n * (band rects are axis-aligned, so tilt/aspect checks are band-specific).\n */\nexport function analyzeDocFrame(input: {\n format: DocFormat;\n quad: DocQuad | null;\n frameWidth: number;\n frameHeight: number;\n measures: DocFrameMeasures;\n thresholds?: Partial<DocGateThresholds>;\n}): DocFrameAnalysis {\n const t = { ...DOC_GATE_V1_DEFAULTS, ...input.thresholds };\n const a = DOC_ANALYSIS_DEFAULTS;\n const { quad, frameWidth, frameHeight, measures } = input;\n const bandMode = input.format === \"ID-3\";\n\n let framing: FramingStatus = \"ok\";\n if (quad === null) {\n framing = \"no_document\";\n } else if (bandMode) {\n const bounds = quadBounds(quad);\n const widthFrac = frameWidth > 0 ? bounds.width / frameWidth : 0;\n const aspect = bounds.height > 0 ? bounds.width / bounds.height : 0;\n if (widthFrac < t.ID3_BAND_MIN_WIDTH_FRAC) framing = \"too_far\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (aspect < t.ID3_BAND_ASPECT_MIN || aspect > t.ID3_BAND_ASPECT_MAX) {\n framing = \"wrong_shape\";\n }\n } else {\n const areaFrac = quadAreaFrac(quad, frameWidth, frameHeight);\n const band = aspectBand(input.format);\n const bounds = quadBounds(quad);\n const aspect =\n Math.max(bounds.width, bounds.height) / Math.max(1e-6, Math.min(bounds.width, bounds.height));\n if (areaFrac < t.MIN_AREA_FRAC) framing = \"too_far\";\n else if (areaFrac > a.MAX_AREA_FRAC) framing = \"too_close\";\n else if (edgeClipped(quad, frameWidth, frameHeight, t.EDGE_CLIP_MARGIN_PX)) {\n framing = \"edge_clipped\";\n } else if (Math.abs(aspect - band.center) > band.tolerance) framing = \"wrong_shape\";\n else if (quadTiltRatio(quad) > a.TILT_MAX_SIDE_RATIO) framing = \"tilted\";\n }\n\n const lighting: LightingStatus =\n measures.exposure >= t.GUIDE_EXPOSURE\n ? \"ok\"\n : measures.meanLuma < (EXPOSURE_BAND[0] + EXPOSURE_BAND[1]) / 2\n ? \"too_dark\"\n : \"too_bright\";\n const blur: BlurStatus = measures.sharpness >= t.GUIDE_SHARPNESS ? \"ok\" : \"blurry\";\n const glare: GlareStatus =\n measures.specularFraction >= a.GLARE_MAX_SPECULAR_FRAC ? \"glare\" : \"ok\";\n\n return {\n framing,\n lighting,\n blur,\n glare,\n acceptable: framing === \"ok\" && lighting === \"ok\" && blur === \"ok\" && glare === \"ok\",\n };\n}\n\nfunction edgeClipped(\n quad: DocQuad,\n frameWidth: number,\n frameHeight: number,\n margin: number,\n): boolean {\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n return pts.some(\n (p) => p.x <= margin || p.y <= margin || p.x >= frameWidth - margin || p.y >= frameHeight - margin,\n );\n}\n","// Feedback aggregator (capture v2, Task-37 round 4) — the BlinkID-inspired\n// anti-flicker layer between per-frame analysis and the UI: each frame VOTES\n// for a feedback state; votes decay exponentially over a sliding window and\n// the DOMINANT state wins, but never before the current state's minimum\n// display duration has elapsed. Success states bypass voting entirely.\n//\n// 100% deterministic: the clock is an input (`nowMs`), the decay math is\n// closed-form — golden-testable tick sequences, no timers.\nimport type { DocFrameAnalysis } from \"./frame-analysis\";\n\n/** Feedback vocabulary (dcv2.* message keys on the hosted side). */\nexport type DocFeedbackKey =\n | \"sensing\"\n | \"too_far\"\n | \"too_close\"\n | \"edge_clipped\"\n | \"wrong_shape\"\n | \"tilted\"\n | \"too_dark\"\n | \"too_bright\"\n | \"blurry\"\n | \"glare\"\n | \"hold_still\"\n | \"processing\"\n | \"side_captured\"\n | \"captured\";\n\n/** Reticle rendering mode per feedback state (the stage maps to visuals). */\nexport type ReticleType = \"searching\" | \"coaching\" | \"locking\" | \"processing\" | \"flip\" | \"done\";\n\nexport interface DocFeedbackState {\n key: DocFeedbackKey;\n reticle: ReticleType;\n /** The state holds the UI at least this long (ms) before a switch. */\n minDurationMs: number;\n}\n\nexport const DOC_FEEDBACK_STATES: Readonly<Record<DocFeedbackKey, DocFeedbackState>> =\n Object.freeze({\n sensing: { key: \"sensing\", reticle: \"searching\", minDurationMs: 900 },\n too_far: { key: \"too_far\", reticle: \"coaching\", minDurationMs: 1400 },\n too_close: { key: \"too_close\", reticle: \"coaching\", minDurationMs: 1400 },\n edge_clipped: { key: \"edge_clipped\", reticle: \"coaching\", minDurationMs: 1400 },\n wrong_shape: { key: \"wrong_shape\", reticle: \"coaching\", minDurationMs: 1400 },\n tilted: { key: \"tilted\", reticle: \"coaching\", minDurationMs: 1400 },\n too_dark: { key: \"too_dark\", reticle: \"coaching\", minDurationMs: 1400 },\n too_bright: { key: \"too_bright\", reticle: \"coaching\", minDurationMs: 1400 },\n blurry: { key: \"blurry\", reticle: \"coaching\", minDurationMs: 1400 },\n glare: { key: \"glare\", reticle: \"coaching\", minDurationMs: 1400 },\n hold_still: { key: \"hold_still\", reticle: \"locking\", minDurationMs: 900 },\n processing: { key: \"processing\", reticle: \"processing\", minDurationMs: 800 },\n side_captured: { key: \"side_captured\", reticle: \"flip\", minDurationMs: 2600 },\n captured: { key: \"captured\", reticle: \"done\", minDurationMs: 800 },\n });\n\n/** Frame analysis → the feedback vote it casts (worst axis speaks). */\nexport function feedbackVoteOf(analysis: DocFrameAnalysis): DocFeedbackKey {\n if (analysis.framing === \"no_document\") return \"sensing\";\n if (analysis.framing === \"too_far\") return \"too_far\";\n if (analysis.framing === \"too_close\") return \"too_close\";\n if (analysis.framing === \"edge_clipped\") return \"edge_clipped\";\n if (analysis.framing === \"wrong_shape\") return \"wrong_shape\";\n if (analysis.framing === \"tilted\") return \"tilted\";\n if (analysis.lighting === \"too_dark\") return \"too_dark\";\n if (analysis.lighting === \"too_bright\") return \"too_bright\";\n if (analysis.glare === \"glare\") return \"glare\";\n if (analysis.blur === \"blurry\") return \"blurry\";\n return \"hold_still\";\n}\n\ninterface Vote {\n key: DocFeedbackKey;\n atMs: number;\n}\n\n/** Sliding vote window + exponential decay + per-state minimum hold. */\nexport class DocFeedbackAggregator {\n private votes: Vote[] = [];\n private current: DocFeedbackState = DOC_FEEDBACK_STATES.sensing;\n private currentSinceMs: number | null = null;\n\n constructor(\n private readonly windowMs = 2600,\n /** Per-second decay factor — recent frames outvote stale ones. */\n private readonly decayPerSecond = 0.35,\n ) {}\n\n reset(): void {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES.sensing;\n this.currentSinceMs = null;\n }\n\n /** Force a terminal/choreography state (capture fired, side flip, upload). */\n force(key: Extract<DocFeedbackKey, \"processing\" | \"side_captured\" | \"captured\">, nowMs: number): DocFeedbackState {\n this.votes = [];\n this.current = DOC_FEEDBACK_STATES[key];\n this.currentSinceMs = nowMs;\n return this.current;\n }\n\n /** Cast this frame's vote and return the (possibly held) winning state. */\n tick(vote: DocFeedbackKey, nowMs: number): DocFeedbackState {\n if (this.currentSinceMs === null) this.currentSinceMs = nowMs;\n this.votes.push({ key: vote, atMs: nowMs });\n this.votes = this.votes.filter((v) => nowMs - v.atMs <= this.windowMs);\n\n // Hold the current state through its minimum display duration.\n if (nowMs - this.currentSinceMs < this.current.minDurationMs) return this.current;\n\n const scores = new Map<DocFeedbackKey, number>();\n for (const v of this.votes) {\n const ageSeconds = (nowMs - v.atMs) / 1000;\n const weight = this.decayPerSecond ** ageSeconds; // 1 now → decays with age\n scores.set(v.key, (scores.get(v.key) ?? 0) + weight);\n }\n let winner: DocFeedbackKey = this.current.key;\n let best = -1;\n for (const [key, score] of scores) {\n if (score > best) {\n best = score;\n winner = key;\n }\n }\n if (winner !== this.current.key) {\n this.current = DOC_FEEDBACK_STATES[winner];\n this.currentSinceMs = nowMs;\n }\n return this.current;\n }\n}\n","// CaptureSessionDoc (Task 34) — the THIN document twin of CaptureSessionV3:\n// environment-facing camera, a 480px detect snapshot at the device profile's\n// cadence, DocGateV1 ticks, and on `ready` an UNCROPPED full-resolution still\n// (gate round 2: client-side quad cropping cut the MRZ when corners were off;\n// kyc-docs-v2 runs its own detector/warp — the client's job is timing, not\n// geometry). Same event contract SHAPE as CaptureSessionV3\n// (`statechange | checks | guidance | artifacts | error`) so hosts bind it\n// with the same hook pattern. Every effect is seam-injectable for tests; the\n// gate itself stays 100% deterministic (clock = tick input).\nimport { mapGetUserMediaError } from \"../media\";\nimport type { CaptureArtifacts, CaptureErrorKind, CaptureState } from \"../types\";\nimport { classifyDevice, type DeviceClass, probeDevice } from \"../v3/device-profile\";\nimport { exposure, meanLuma, sharpness } from \"../v3/frame-quality\";\nimport {\n DocFeedbackAggregator,\n type DocFeedbackState,\n feedbackVoteOf,\n} from \"./feedback-aggregator\";\nimport { analyzeDocFrame, type DocFrameAnalysis, specularFraction } from \"./frame-analysis\";\nimport type { DocFormat, DocSide } from \"./doc-catalog\";\nimport {\n DOC_DETECT_WIDTH,\n DocGateV1,\n type DocGateThresholds,\n type DocGateTick,\n type DocQuad,\n} from \"./doc-gate-v1\";\nimport { detectMrzBand } from \"./mrz-band\";\n\n/**\n * LAZY scanic load (route-budget contract): the vendored detector is ~39KiB gz\n * and must NOT ride the host's session-shell bundle — it is dynamically\n * imported on the FIRST detect tick (the camera-permission prompt gives the\n * fetch plenty of cover). Tests inject `opts.detect` and never touch this.\n */\nlet scanicDetect: ((image: ImageData) => Promise<DocQuad | null>) | null = null;\nasync function lazyScanicDetect(image: ImageData): Promise<DocQuad | null> {\n if (scanicDetect === null) {\n const module_ = await import(\"./detector-scanic\");\n scanicDetect = module_.detectQuad;\n }\n return scanicDetect(image);\n}\n\n/**\n * ID-3 detection (round 3): the MRZ stroke band, NOT page-edge quads — an\n * open booklet's page edges are white-on-white and defeat contour detection\n * (operator field finding, twice). The band rect becomes the gate's \"quad\"\n * (ID-3 band-mode geometry). Pure JS — no scanic on the passport path.\n */\nasync function mrzBandDetect(image: ImageData): Promise<DocQuad | null> {\n const band = detectMrzBand(image);\n if (band === null) return null;\n return {\n topLeft: { x: band.x, y: band.y },\n topRight: { x: band.x + band.width, y: band.y },\n bottomRight: { x: band.x + band.width, y: band.y + band.height },\n bottomLeft: { x: band.x, y: band.y + band.height },\n };\n}\n\n/**\n * Pre-fetch + cache the scanic chunk (call when the scan surface MOUNTS, before\n * the camera is even granted) so the first detect tick pays no chunk-fetch\n * latency. Fire-and-forget; failures fall back to the lazy path.\n */\nexport function warmDocDetector(): void {\n if (scanicDetect !== null) return;\n void import(\"./detector-scanic\")\n .then((module_) => {\n scanicDetect = module_.detectQuad;\n })\n .catch(() => undefined);\n}\n\n/** Doc detect cadence per device class — scanic @480px is ~3–8× a face tick,\n * so the doc loop paces slower than the face profiles (findings §1: 2–5 Hz). */\nexport const DOC_CADENCE_HZ: Readonly<Record<DeviceClass, number>> = Object.freeze({\n LOW: 3,\n MID: 5,\n HIGH: 6,\n});\n\nexport interface DocChecksPayload {\n engine: \"docv1\";\n /** Gate integrator 0..1 — the overlay ring. */\n p: number;\n plausible: boolean;\n stableTicks: number;\n quad: DocQuad | null;\n /** Detect-frame size the quad coordinates live in (overlay scaling). */\n frameWidth: number;\n frameHeight: number;\n /** Capture-v2 per-frame status vector (framing/lighting/blur/glare). */\n analysis: DocFrameAnalysis;\n}\n\n/** Artifacts: `keyframe` = the full-res still of the framed side (JPEG). */\nexport type DocArtifacts = CaptureArtifacts & { side?: DocSide };\n\nexport interface CaptureSessionDocEvents {\n statechange: { state: CaptureState };\n checks: DocChecksPayload;\n /** dcv1.* message KEY — hosts translate; never prose. Deduped. */\n guidance: { key: string };\n /** Capture-v2 AGGREGATED feedback (time-decay voted, min-duration held) —\n * the stage's message + reticle channel. Deduped on state key. */\n feedback: DocFeedbackState;\n artifacts: DocArtifacts;\n error: { kind: CaptureErrorKind; retryable: boolean };\n}\n\nexport interface CaptureSessionDocOptions {\n /** Host-owned <video playsInline muted>. */\n video: HTMLVideoElement;\n format?: DocFormat;\n /** Side label stamped on the artifacts event (recto→verso sequencing). */\n side?: DocSide;\n thresholds?: Partial<DocGateThresholds>;\n deviceClass?: DeviceClass;\n // ── seams (production defaults in parens) ─────────────────────────────────\n /** (navigator.mediaDevices.getUserMedia — environment camera) */\n getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;\n /** (vendored scanic detectQuad) */\n detect?: (image: ImageData) => Promise<DocQuad | null>;\n /** (canvas downscale of the live video to the 480px detect frame) */\n grabDetectFrame?: (video: HTMLVideoElement, targetWidth: number) => ImageData | null;\n /** (full-res canvas still → JPEG blob + preview; quad passed for telemetry) */\n captureStill?: (\n video: HTMLVideoElement,\n quad: DocQuad,\n detectSize: { width: number; height: number },\n ) => Promise<{ blob: Blob; previewDataUrl: string }>;\n /** (performance.now) */\n now?: () => number;\n /** (setTimeout/clearTimeout) — the cadence pacer. */\n setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimer?: (id: ReturnType<typeof setTimeout>) => void;\n}\n\ntype Handler<K extends keyof CaptureSessionDocEvents> = (\n payload: CaptureSessionDocEvents[K],\n) => void;\n\nexport class CaptureSessionDoc {\n private readonly opts: CaptureSessionDocOptions;\n private readonly gate: DocGateV1;\n private readonly cadenceMs: number;\n private readonly handlers = new Map<string, Set<(payload: never) => void>>();\n private stream: MediaStream | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private state: CaptureState = \"idle\";\n private lastGuidance: string | null = null;\n private attempt = 0;\n private capturing = false;\n private readonly feedback = new DocFeedbackAggregator();\n private lastFeedbackKey: string | null = null;\n\n constructor(opts: CaptureSessionDocOptions) {\n this.opts = opts;\n this.gate = new DocGateV1(opts.format ?? \"ID-1\", opts.thresholds);\n const deviceClass = opts.deviceClass ?? classifyDevice(probeDevice());\n this.cadenceMs = 1000 / DOC_CADENCE_HZ[deviceClass];\n }\n\n on<K extends keyof CaptureSessionDocEvents>(event: K, handler: Handler<K>): () => void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler as (payload: never) => void);\n return () => set?.delete(handler as (payload: never) => void);\n }\n\n private emit<K extends keyof CaptureSessionDocEvents>(\n event: K,\n payload: CaptureSessionDocEvents[K],\n ): void {\n for (const handler of [...(this.handlers.get(event) ?? [])]) {\n (handler as Handler<K>)(payload);\n }\n }\n\n async start(): Promise<void> {\n const attempt = ++this.attempt;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n this.setState(\"preflight\");\n try {\n const getUserMedia =\n this.opts.getUserMedia ??\n ((constraints: MediaStreamConstraints) =>\n navigator.mediaDevices.getUserMedia(constraints));\n this.setState(\"acquiring\");\n this.stream = await getUserMedia({\n audio: false,\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n });\n if (attempt !== this.attempt) {\n for (const track of this.stream.getTracks()) track.stop();\n return;\n }\n this.opts.video.srcObject = this.stream;\n await this.opts.video.play().catch(() => undefined);\n this.setState(\"positioning\");\n this.scheduleTick(attempt);\n } catch (err) {\n const kind = mapGetUserMediaError(err);\n this.setState(\"error\");\n this.emit(\"error\", { kind, retryable: true });\n }\n }\n\n /** Restart the gate for another side / a retake (same stream). */\n rearm(side?: DocSide): void {\n if (side !== undefined) this.opts.side = side;\n this.gate.reset();\n this.feedback.reset();\n this.lastFeedbackKey = null;\n this.lastGuidance = null;\n this.capturing = false;\n if (this.state === \"captured\") {\n this.setState(\"positioning\");\n this.scheduleTick(this.attempt);\n }\n }\n\n destroy(): void {\n this.attempt += 1;\n if (this.timer !== null) (this.opts.clearTimer ?? clearTimeout)(this.timer);\n this.timer = null;\n if (this.stream) for (const track of this.stream.getTracks()) track.stop();\n this.stream = null;\n this.opts.video.srcObject = null;\n }\n\n private setState(state: CaptureState): void {\n this.state = state;\n this.emit(\"statechange\", { state });\n }\n\n private scheduleTick(attempt: number): void {\n const setTimer = this.opts.setTimer ?? setTimeout;\n this.timer = setTimer(() => void this.tick(attempt), this.cadenceMs);\n }\n\n private async tick(attempt: number): Promise<void> {\n if (attempt !== this.attempt || this.state !== \"positioning\" || this.capturing) return;\n const now = this.opts.now ?? (() => performance.now());\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const detect =\n this.opts.detect ??\n ((this.opts.format ?? \"ID-1\") === \"ID-3\" ? mrzBandDetect : lazyScanicDetect);\n\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) {\n this.scheduleTick(attempt);\n return;\n }\n let quad: DocQuad | null = null;\n try {\n quad = await detect(frame);\n } catch {\n quad = null; // a detector hiccup is a no-doc tick, never a crash\n }\n if (attempt !== this.attempt) return;\n\n const crop = quad !== null ? cropQuadRegion(frame, quad) : frame;\n const atMs = now();\n const measures = {\n sharpness: sharpness(crop),\n exposure: exposure(crop),\n meanLuma: meanLuma(crop),\n specularFraction: specularFraction(crop),\n };\n const analysis = analyzeDocFrame({\n format: this.opts.format ?? \"ID-1\",\n quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n measures,\n ...(this.opts.thresholds !== undefined ? { thresholds: this.opts.thresholds } : {}),\n });\n const tick = this.gate.tick({\n timestampMs: atMs,\n quad,\n quality: measures,\n frameWidth: frame.width,\n frameHeight: frame.height,\n });\n this.emit(\"checks\", {\n engine: \"docv1\",\n p: tick.p,\n plausible: tick.plausible,\n stableTicks: tick.stableTicks,\n quad: tick.quad,\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysis,\n });\n // Capture-v2 feedback: time-decay voted + min-duration held (anti-flicker).\n const feedbackState = this.feedback.tick(feedbackVoteOf(analysis), atMs);\n if (feedbackState.key !== this.lastFeedbackKey) {\n this.lastFeedbackKey = feedbackState.key;\n this.emit(\"feedback\", feedbackState);\n }\n if (tick.guidanceKey !== this.lastGuidance) {\n this.lastGuidance = tick.guidanceKey;\n this.emit(\"guidance\", { key: tick.guidanceKey });\n }\n\n if (tick.ready && quad !== null) {\n const processing = this.feedback.force(\"processing\", atMs);\n this.lastFeedbackKey = processing.key;\n this.emit(\"feedback\", processing);\n await this.fire(attempt, quad, {\n width: frame.width,\n height: frame.height,\n tickSharpness: measures.sharpness,\n });\n return;\n }\n this.scheduleTick(attempt);\n }\n\n /**\n * BEST-FRAME capture window (capture-v2): the first ready tick captures a\n * candidate immediately, then up to CAPTURE_EXTRA_CANDIDATES more stills are\n * taken at CAPTURE_CANDIDATE_SPACING_MS while the user holds — each scored\n * by the live detect-tick sharpness at its capture moment — and the SHARPEST\n * wins. Beats first-acceptable-frame capture without decoding stills.\n */\n private async fire(\n attempt: number,\n quad: DocQuad,\n detectSize: { width: number; height: number; tickSharpness: number },\n ): Promise<void> {\n this.capturing = true;\n const capture = this.opts.captureStill ?? defaultCaptureStill;\n const grab = this.opts.grabDetectFrame ?? defaultGrabDetectFrame;\n const setTimer = this.opts.setTimer ?? setTimeout;\n const size = { width: detectSize.width, height: detectSize.height };\n try {\n let best = {\n still: await capture(this.opts.video, quad, size),\n sharpness: detectSize.tickSharpness,\n };\n for (let i = 0; i < CAPTURE_EXTRA_CANDIDATES; i++) {\n await new Promise<void>((resolve) => {\n setTimer(() => resolve(), CAPTURE_CANDIDATE_SPACING_MS);\n });\n if (attempt !== this.attempt) return;\n const frame = grab(this.opts.video, DOC_DETECT_WIDTH);\n if (frame === null) continue;\n const candidateSharpness = sharpness(cropQuadRegion(frame, quad));\n if (candidateSharpness > best.sharpness * CAPTURE_IMPROVEMENT_MIN_RATIO) {\n const still = await capture(this.opts.video, quad, size);\n if (attempt !== this.attempt) return;\n best = { still, sharpness: candidateSharpness };\n }\n }\n if (attempt !== this.attempt) return;\n this.setState(\"captured\");\n this.emit(\"artifacts\", {\n keyframe: best.still.blob,\n previewDataUrl: best.still.previewDataUrl,\n ...(this.opts.side !== undefined ? { side: this.opts.side } : {}),\n });\n } catch {\n if (attempt !== this.attempt) return;\n // Still capture failed (canvas/encode) — rearm the gate and keep coaching.\n this.capturing = false;\n this.gate.reset();\n this.scheduleTick(attempt);\n }\n }\n}\n\n/** Best-frame window: 2 extra candidates, 260ms apart, ≥8% sharper to replace. */\nexport const CAPTURE_EXTRA_CANDIDATES = 2;\nexport const CAPTURE_CANDIDATE_SPACING_MS = 260;\nconst CAPTURE_IMPROVEMENT_MIN_RATIO = 1.08;\n\n/** Downscale the live video to the detect width (aspect preserved). */\nfunction defaultGrabDetectFrame(video: HTMLVideoElement, targetWidth: number): ImageData | null {\n const vw = video.videoWidth;\n const vh = video.videoHeight;\n if (vw === 0 || vh === 0) return null;\n const scale = Math.min(1, targetWidth / vw);\n const width = Math.round(vw * scale);\n const height = Math.round(vh * scale);\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (context === null) return null;\n context.drawImage(video, 0, 0, width, height);\n return context.getImageData(0, 0, width, height);\n}\n\n/**\n * Capture the FULL-RES frame (gate-timed, UNCROPPED — operator gate round 2):\n * client-side quad cropping cut the MRZ whenever the detected corners were\n * slightly off, and kyc-docs-v2 runs its OWN detector/warp on the server —\n * the client's job is TIMING (fire when framed/steady), not geometry. The\n * quad/detectSize params stay in the seam signature for telemetry/tests.\n */\nasync function defaultCaptureStill(\n video: HTMLVideoElement,\n _quad: DocQuad,\n _detectSize: { width: number; height: number },\n): Promise<{ blob: Blob; previewDataUrl: string }> {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, video.videoWidth);\n canvas.height = Math.max(1, video.videoHeight);\n const context = canvas.getContext(\"2d\");\n if (context === null) throw new Error(\"2d context unavailable\");\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error(\"toBlob failed\"))), \"image/jpeg\", 0.92);\n });\n return { blob, previewDataUrl: canvas.toDataURL(\"image/jpeg\", 0.85) };\n}\n\n/** Quad-bbox crop of the DETECT frame — the quality measures run on this. */\nfunction cropQuadRegion(frame: ImageData, quad: DocQuad): ImageData {\n const xs = [quad.topLeft.x, quad.topRight.x, quad.bottomRight.x, quad.bottomLeft.x];\n const ys = [quad.topLeft.y, quad.topRight.y, quad.bottomRight.y, quad.bottomLeft.y];\n const x1 = Math.max(0, Math.floor(Math.min(...xs)));\n const y1 = Math.max(0, Math.floor(Math.min(...ys)));\n const x2 = Math.min(frame.width, Math.ceil(Math.max(...xs)));\n const y2 = Math.min(frame.height, Math.ceil(Math.max(...ys)));\n const width = Math.max(1, x2 - x1);\n const height = Math.max(1, y2 - y1);\n const data = new Uint8ClampedArray(width * height * 4);\n for (let row = 0; row < height; row++) {\n const src = ((y1 + row) * frame.width + x1) * 4;\n data.set(frame.data.subarray(src, src + width * 4), row * width * 4);\n }\n return { data, width, height, colorSpace: frame.colorSpace } as ImageData;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/verification/emitter.ts","../src/capture/create-liveness-capture.ts","../src/verification/result.ts","../src/verification/liveness-flow.ts","../src/verification/turnstile.ts","../src/verification/create-verification.ts"],"sourcesContent":["// Tiny typed emitter (zero deps). Handlers are snapshotted before dispatch so an\n// unsubscribe from inside a handler never skips a sibling.\n\nexport interface Emitter<Events extends object> {\n on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void): () => void;\n emit<K extends keyof Events>(event: K, payload: Events[K]): void;\n clear(): void;\n}\n\nexport function createEmitter<Events extends object>(): Emitter<Events> {\n const handlers = new Map<keyof Events, Set<(payload: never) => void>>();\n return {\n on(event, handler) {\n let set = handlers.get(event);\n if (set === undefined) {\n set = new Set();\n handlers.set(event, set);\n }\n const h = handler as (payload: never) => void;\n set.add(h);\n return () => {\n set?.delete(h);\n };\n },\n emit(event, payload) {\n const set = handlers.get(event);\n if (set === undefined) return;\n for (const handler of [...set]) {\n (handler as (p: Events[typeof event]) => void)(payload);\n }\n },\n clear() {\n handlers.clear();\n },\n };\n}\n","// createLivenessCapture — the public, framework-free capture handle (spec\n// §3.1) over the calibration-pinned CaptureSessionV3 engine. It MAPS the\n// engine's event surface (statechange / checks / guidance / artifacts / error /\n// frozen) onto the partner contract (state / gate / captured / error) and owns\n// the NO_RECORDER degrade (photo mode → keyframe-only `captured`). Pinned\n// capture constants stay inside the engine — nothing here is configurable.\nimport { detectIsMobile } from \"../core/capture-constraints\";\nimport { isMediaRecorderSupported } from \"../core/media\";\nimport type { CaptureErrorKind as EngineErrorKind, CaptureState as EngineState } from \"../core/types\";\nimport {\n CaptureSessionV3,\n type CaptureSessionV3Events,\n type CaptureSessionV3Options,\n} from \"../core/v3/capture-session-v3\";\nimport { createEmitter } from \"../verification/emitter\";\nimport type {\n CaptureErrorKind,\n CaptureState,\n GateHint,\n LivenessCaptureEvents,\n LivenessCaptureHandle,\n LivenessCaptureOptions,\n} from \"../verification/types\";\n\n/** The engine surface the handle binds — CaptureSessionV3 or a test double. */\nexport interface CaptureEngine {\n readonly state: EngineState;\n on<K extends keyof CaptureSessionV3Events>(\n event: K,\n handler: (payload: CaptureSessionV3Events[K]) => void,\n ): () => void;\n start(): Promise<void>;\n retry(): void;\n switchCamera(): void;\n destroy(): void;\n /** Device-profile chrome hint (LOW class → `'reduced'`); absent on doubles. */\n readonly animations?: \"full\" | \"reduced\";\n}\n\n/** Internal construction seams (tests; Sekyro's own hosted page). */\nexport interface LivenessCaptureInternals {\n /** Replace the engine constructor (default: `new CaptureSessionV3(options)`). */\n createEngine?: (options: CaptureSessionV3Options) => CaptureEngine;\n /** Extra engine options merged in (worker offload, warmed detector, telemetry). */\n engineOptions?: Partial<\n Pick<CaptureSessionV3Options, \"createDetector\" | \"createWorker\" | \"telemetry\" | \"deviceClass\">\n >;\n /** Clock (default `performance.now`). */\n now?: () => number;\n}\n\n/** fcv3.* guidance key → public gate hint code. */\nexport function mapGuidanceHint(key: string): GateHint | null {\n switch (key) {\n case \"fcv3.no_face\":\n case \"fcv3.center_face\":\n case \"fcv3.straighten\":\n return \"center\";\n case \"fcv3.come_closer\":\n return \"closer\";\n case \"fcv3.move_back\":\n return \"farther\";\n case \"fcv3.more_light\":\n case \"fcv3.too_bright\":\n return \"light\";\n case \"fcv3.hold_still\":\n case \"fcv3.steady_camera\":\n return \"still\";\n default:\n return null;\n }\n}\n\n/** Engine state → public state (`preflight|acquiring → starting`, `positioning → gating`). */\nexport function mapEngineState(state: EngineState): CaptureState {\n switch (state) {\n case \"preflight\":\n case \"acquiring\":\n return \"starting\";\n case \"positioning\":\n return \"gating\";\n case \"idle\":\n case \"recording\":\n case \"captured\":\n case \"error\":\n return state;\n default: {\n const unhandled: never = state;\n throw new Error(`mapEngineState: unhandled state ${unhandled as string}`);\n }\n }\n}\n\n/** Engine error kinds → the public six (`models|cliptoolarge|recorder|overconstrained → generic`). */\nexport function mapErrorKind(kind: EngineErrorKind): CaptureErrorKind {\n switch (kind) {\n case \"permission\":\n case \"notfound\":\n case \"inuse\":\n case \"insecure\":\n case \"unsupported\":\n return kind;\n case \"overconstrained\":\n case \"generic\":\n case \"models\":\n case \"cliptoolarge\":\n case \"recorder\":\n return \"generic\";\n default: {\n const unhandled: never = kind;\n throw new Error(`mapErrorKind: unhandled kind ${unhandled as string}`);\n }\n }\n}\n\n/** Handle + the hooks the verification layer uses to drive the flow states. */\nexport interface LivenessCaptureInternalHandle {\n handle: LivenessCaptureHandle;\n /** Push a post-capture state (`uploading | checking | done`). */\n setFlowState(state: Extract<CaptureState, \"uploading\" | \"checking\" | \"done\">): void;\n}\n\nexport function createLivenessCaptureInternal(\n options: LivenessCaptureOptions,\n internals: LivenessCaptureInternals = {},\n): LivenessCaptureInternalHandle {\n const now = internals.now ?? (() => performance.now());\n const emitter = createEmitter<LivenessCaptureEvents>();\n // NO_RECORDER degrade: no MediaRecorder → photo mode (keyframe only). Decided\n // ONCE at construction; the artifacts carry the flag.\n const degraded = !isMediaRecorderSupported();\n const engineOptions: CaptureSessionV3Options = {\n video: options.video,\n mode: degraded ? \"photo\" : \"clip\",\n ...(options.facing !== undefined ? { facingMode: options.facing } : {}),\n ...(options.assetBaseUrl !== undefined ? { v3AssetBaseUrl: options.assetBaseUrl } : {}),\n ...(internals.engineOptions ?? {}),\n };\n const engine: CaptureEngine =\n internals.createEngine?.(engineOptions) ?? new CaptureSessionV3(engineOptions);\n\n let state: CaptureState = \"idle\";\n let hint: GateHint | null = null;\n let progress = 0;\n let faceDetected = false;\n let animations: \"full\" | \"reduced\" = \"full\";\n let recordingStartedAt: number | null = null;\n let frozenAt: number | null = null;\n // Frame size latched while the stream is LIVE: the engine stops the tracks\n // before it emits `artifacts`, and iOS Safari then reports videoWidth /\n // videoHeight as 0 (field: comparison records with width 0 / height 0).\n let frameWidth = 0;\n let frameHeight = 0;\n const latchFrameSize = (): void => {\n if (options.video.videoWidth > 0 && options.video.videoHeight > 0) {\n frameWidth = options.video.videoWidth;\n frameHeight = options.video.videoHeight;\n }\n };\n\n const setState = (next: CaptureState): void => {\n state = next;\n emitter.emit(\"state\", next);\n };\n const emitGate = (): void => emitter.emit(\"gate\", { progress, hint, faceDetected });\n // The LOW-profile hint settles once the engine probes (statechange) and may\n // flip mid-positioning on a dynamic demotion (per checks tick) — emit on change.\n const syncProfile = (): void => {\n const next = engine.animations ?? \"full\";\n if (next === animations) return;\n animations = next;\n emitter.emit(\"profile\", { animations });\n };\n\n const unsubscribes = [\n engine.on(\"statechange\", ({ state: engineState }) => {\n const next = mapEngineState(engineState);\n if (next === \"starting\" && state !== \"starting\") {\n // Fresh attempt: gate chrome resets.\n hint = null;\n progress = 0;\n faceDetected = false;\n recordingStartedAt = null;\n frozenAt = null;\n frameWidth = 0;\n frameHeight = 0;\n }\n setState(next);\n syncProfile();\n if (next === \"recording\") {\n latchFrameSize();\n recordingStartedAt = now();\n hint = \"hold\";\n progress = 1;\n emitGate();\n }\n }),\n engine.on(\"checks\", (payload) => {\n latchFrameSize(); // the engine only ticks on a live frame (videoWidth > 0)\n progress = payload.progress;\n faceDetected = payload.factors.presence > 0;\n emitGate();\n syncProfile();\n }),\n engine.on(\"guidance\", ({ key }) => {\n hint = mapGuidanceHint(key);\n emitGate();\n }),\n engine.on(\"frozen\", ({ previewDataUrl }) => {\n frozenAt = now();\n emitter.emit(\"frozen\", { previewDataUrl });\n }),\n engine.on(\"artifacts\", (artifacts) => {\n const clipMs =\n recordingStartedAt !== null && frozenAt !== null\n ? Math.max(0, Math.round(frozenAt - recordingStartedAt))\n : undefined;\n emitter.emit(\"captured\", {\n keyframe: artifacts.keyframe,\n ...(artifacts.clip !== undefined ? { clip: artifacts.clip } : {}),\n ...(artifacts.clipMimeType !== undefined ? { clipMime: artifacts.clipMimeType } : {}),\n width: options.video.videoWidth || frameWidth,\n height: options.video.videoHeight || frameHeight,\n ...(clipMs !== undefined ? { clipMs } : {}),\n captureChannel: detectIsMobile() ? \"mobile\" : \"desktop\",\n ...(degraded ? { degraded: \"NO_RECORDER\" as const } : {}),\n engine: \"v3\",\n // Face-capture timing: the engine's marks, forwarded flat (absent → absent).\n ...(artifacts.timing?.cameraReadyMs !== undefined\n ? { cameraReadyMs: artifacts.timing.cameraReadyMs }\n : {}),\n ...(artifacts.timing?.faceAcquireMs !== undefined\n ? { faceAcquireMs: artifacts.timing.faceAcquireMs }\n : {}),\n ...(artifacts.timing?.captureMs !== undefined\n ? { captureMs: artifacts.timing.captureMs }\n : {}),\n });\n }),\n engine.on(\"error\", ({ kind, retryable }) => {\n emitter.emit(\"error\", { kind: mapErrorKind(kind), retryable });\n }),\n ];\n\n const handle: LivenessCaptureHandle = {\n get state() {\n return state;\n },\n on: (event, handler) => emitter.on(event, handler),\n start: () => engine.start(),\n retry: () => engine.retry(),\n switchCamera: () => engine.switchCamera(),\n destroy: () => {\n for (const unsubscribe of unsubscribes) unsubscribe();\n engine.destroy();\n emitter.clear();\n },\n };\n\n return {\n handle,\n setFlowState: (next) => {\n if (state !== next) setState(next);\n },\n };\n}\n\n/**\n * Create a standalone liveness capture (no upload — pair it with your own\n * submission, or use {@link Verification.liveness} for the full flow).\n */\nexport function createLivenessCapture(options: LivenessCaptureOptions): LivenessCaptureHandle {\n return createLivenessCaptureInternal(options).handle;\n}\n","// Result mapping: server view → the public VerificationResult (spec §3.1) plus\n// the view helpers the flow needs (frontier, terminal, view merge).\nimport type {\n CheckReason,\n CheckType,\n HostedSessionView,\n HostedStepView,\n SessionView,\n} from \"../client/types\";\nimport type { ReasonCode, VerificationResult } from \"./types\";\n\n/**\n * Reasons the backend parks at `input_required` while attempts remain\n * (identity-backend/src/checks/check-reasons.ts isRetryable tables — the single\n * source of truth; identity-hosted/lib/reason-copy.ts mirrors the same rows).\n */\nexport const RETRYABLE_REASONS: ReadonlySet<CheckReason> = new Set<CheckReason>([\n \"no_face\",\n \"media_unreadable\",\n \"clip_too_short\",\n \"no_face_source\",\n \"side_missing\",\n \"unreadable\",\n \"registry_unavailable\",\n \"otp_mismatch\",\n \"otp_expired\",\n \"otp_send_failed\",\n]);\n\n/** The two checks one biometric capture feeds. */\nconst BIOMETRIC_CHECKS: ReadonlySet<CheckType> = new Set<CheckType>([\"liveness\", \"face_match\"]);\n\n/** A check the browser may still submit. */\nexport function isExecutable(step: HostedStepView | undefined): boolean {\n return step !== undefined && (step.state === \"pending\" || step.state === \"input_required\");\n}\n\n/** The session accepts no further submissions. */\nexport function isTerminalView(view: Pick<HostedSessionView, \"status\">): boolean {\n return view.status !== \"created\" && view.status !== \"in_progress\";\n}\n\n/** First step in `steps[]` order with state `pending | input_required` (the backend's frontier). */\nexport function frontierStep(view: HostedSessionView): HostedStepView | undefined {\n return view.steps.find((step) => isExecutable(step));\n}\n\nexport function stepOf(view: HostedSessionView, checkType: CheckType): HostedStepView | undefined {\n return view.steps.find((step) => step.checkType === checkType);\n}\n\n/**\n * Fold a submit response (partner `SessionView`: no attemptsRemaining) into the\n * hosted view: status/outcome/step states+reasons come from the response; the\n * submitted check's `attemptsRemaining` is decremented locally (every accepted\n * submit consumes exactly one attempt server-side — `max(0, cap - attempts)`).\n * This keeps the happy path free of extra `GET /hosted/session` round-trips.\n */\nexport function mergeSessionView(\n prev: HostedSessionView,\n next: SessionView,\n submitted: CheckType,\n): HostedSessionView {\n const byType = new Map(prev.steps.map((step) => [step.checkType, step] as const));\n const steps: HostedStepView[] = next.steps.map((step) => {\n const before = byType.get(step.checkType);\n const attemptsBefore = before?.attemptsRemaining ?? 0;\n const merged: HostedStepView = {\n ...(before ?? {}),\n checkType: step.checkType,\n state: step.state,\n attemptsRemaining:\n step.checkType === submitted ? Math.max(0, attemptsBefore - 1) : attemptsBefore,\n };\n if (step.reason !== undefined) merged.reason = step.reason;\n else delete merged.reason;\n return merged;\n });\n return { ...prev, status: next.status, outcome: next.outcome, steps, expiresAt: next.expiresAt };\n}\n\nexport function unavailable(sessionId: string, reason?: ReasonCode): VerificationResult {\n return reason === undefined\n ? { sessionId, status: \"unavailable\" }\n : { sessionId, status: \"unavailable\", reason };\n}\n\nfunction withReason(\n sessionId: string,\n status: VerificationResult[\"status\"],\n reason: CheckReason | undefined,\n): VerificationResult {\n return reason === undefined ? { sessionId, status } : { sessionId, status, reason };\n}\n\n/**\n * Map the session view to the public result:\n * - `completed/approved → approved`, `completed/review` or `review_pending → review`,\n * `failed` or `completed/rejected → rejected` (reason = the deciding step's);\n * - `expired → unavailable` (no reason);\n * - in flight: a biometric step parked at `input_required` with a retryable\n * reason → `retry` when `autoRetry` and attempts remain, else `rejected`\n * (the partner opted out of retries — this capture was refused); a step in\n * `review` with nothing executable left → `review`; otherwise `unavailable`\n * (a non-biometric frontier the SDK cannot drive, or still processing).\n */\nexport function mapSessionResult(\n view: HostedSessionView,\n opts: { autoRetry: boolean },\n): VerificationResult {\n const { sessionId } = view;\n switch (view.status) {\n case \"completed\":\n if (view.outcome === \"approved\") return { sessionId, status: \"approved\" };\n if (view.outcome === \"review\") return withReason(sessionId, \"review\", reasonIn(view, \"review\"));\n return withReason(sessionId, \"rejected\", reasonIn(view, \"failed\"));\n case \"failed\":\n return withReason(sessionId, \"rejected\", reasonIn(view, \"failed\"));\n case \"review_pending\":\n return withReason(sessionId, \"review\", reasonIn(view, \"review\"));\n case \"expired\":\n return unavailable(sessionId);\n case \"created\":\n case \"in_progress\":\n break;\n default: {\n const unhandled: never = view.status;\n throw new Error(`mapSessionResult: unhandled status ${unhandled as string}`);\n }\n }\n\n const parked = view.steps.find(\n (step) =>\n BIOMETRIC_CHECKS.has(step.checkType) &&\n step.state === \"input_required\" &&\n step.reason !== undefined,\n );\n if (parked?.reason !== undefined) {\n const canRetry =\n opts.autoRetry && RETRYABLE_REASONS.has(parked.reason) && parked.attemptsRemaining > 0;\n return {\n sessionId,\n status: canRetry ? \"retry\" : \"rejected\",\n reason: parked.reason,\n attemptsRemaining: parked.attemptsRemaining,\n };\n }\n if (frontierStep(view) === undefined && view.steps.some((step) => step.state === \"review\")) {\n return withReason(sessionId, \"review\", reasonIn(view, \"review\"));\n }\n return unavailable(sessionId);\n}\n\n/** Reason of the first step in the given state (the deciding check). */\nfunction reasonIn(view: HostedSessionView, state: HostedStepView[\"state\"]): CheckReason | undefined {\n return view.steps.find((step) => step.state === state && step.reason !== undefined)?.reason;\n}\n","// The post-capture flow, driven by the CURRENT hosted frontier: when `liveness`\n// is the frontier → upload the clip → attestation → submit `liveness`, then\n// `face_match` from this capture's keyframe; when liveness already settled and\n// `face_match` is the frontier → keyframe + `face_match` only; nothing the SDK\n// can drive → settled (the caller maps the view). A RESYNC 409\n// (check_already_finalized / check_out_of_order / stale frontier) re-GETs the\n// view and RE-ENTERS the same frontier logic (bounded) instead of giving up —\n// a liveness that passed under an earlier run still gets its face_match.\n// Logic ported (not the React) from\n// identity-hosted/lib/use-submit.ts (idempotency-key lifecycle, same-key\n// transport replay, one-shot consent_required recovery) and\n// lib/use-capture-submission.ts + components/steps/liveness-step.tsx (the\n// `buildSubmit` executor closures and the face_match chain).\n//\n// Per-submission caches (promoted media key, attestation) are CLOSURE LOCALS of\n// each executor: a same-key replay re-runs the executor with the caches intact\n// (byte-identical body — a re-presign or a fresh token under the same key would\n// be an idempotency_key_collision); fresh artifacts build fresh closures.\nimport { HostedApiError } from \"../client/errors\";\nimport { type HostedClient, stripMimeParams } from \"../client/hosted-client\";\nimport type {\n AttestationInput,\n CheckType,\n HostedSessionView,\n LivenessCaptureInput,\n SessionView,\n} from \"../client/types\";\nimport { frontierStep, isTerminalView, stepOf } from \"./result\";\nimport type { CapturedArtifacts, SdkFailureReason } from \"./types\";\n\n/** The client surface the flow needs — `HostedClient` or a test fake. */\nexport type HostedClientLike = Pick<\n HostedClient,\n \"getSession\" | \"recordConsent\" | \"presign\" | \"uploadToSpaces\" | \"completeMedia\" | \"submitCheck\"\n>;\n\n/** The presign policy allowlist for `kind: 'clip'` (media.policy.ts mirror). */\nexport const CLIP_MIME_ALLOWLIST: readonly string[] = [\"video/webm\", \"video/mp4\"];\n\n/** The engine's keyframe encode (captureMirroredKeyframe → canvas JPEG). */\nconst KEYFRAME_MIME = \"image/jpeg\";\n\n/** Same-key transport replays AFTER the first try, and the pause between them. */\nexport const TRANSPORT_REPLAY_ATTEMPTS = 2;\nexport const TRANSPORT_REPLAY_DELAY_MS = 1_000;\n/** Cap on honouring a 429's Retry-After before giving up on this capture. */\nconst MAX_RETRY_AFTER_MS = 10_000;\n/**\n * Bound on frontier re-entries after a RESYNC code per capture (each re-entry\n * re-GETs the view; each executable check is submitted at most once per capture).\n */\nexport const RESYNC_REENTRY_MAX = 3;\n\n/** No (usable) response arrived — a same-key replay can succeed. */\nconst TRANSPORT_CODES: ReadonlySet<string> = new Set([\n \"network_error\",\n \"timeout\",\n \"upload_failed\",\n \"unknown_error\",\n \"rate_limit_exceeded\",\n]);\n\n/** The server moved past what we hold — re-GET and map from truth. */\nconst RESYNC_CODES: ReadonlySet<string> = new Set([\n \"session_not_active\",\n \"check_not_found\",\n \"check_already_finalized\",\n \"check_out_of_order\",\n]);\n\n/** A Turnstile challenge failed client-side (load / render / error-callback / timeout). */\nexport class AttestationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AttestationError\";\n }\n}\n\n/** One logical submission: receives the client and ITS idempotency key. */\nexport type SubmitExecutor = (\n client: HostedClientLike,\n idempotencyKey: string,\n) => Promise<SessionView>;\n\nexport interface SubmitRuntime {\n client: HostedClientLike;\n /** Force-record consent (the server just said it is missing). */\n ensureConsent(opts: { force: true }): Promise<void>;\n sleep(ms: number): Promise<void>;\n uuid(): string;\n}\n\n/**\n * Run one logical submission (ported use-submit semantics):\n * - a fresh `crypto.randomUUID()` key per logical submission;\n * - a transport-class failure replays the SAME executor under the SAME key\n * (bounded) — the backend's IdempotencyInterceptor returns the first\n * response instead of burning another attempt;\n * - ONE `consent_required` recovery: force-record consent and re-run under a\n * FRESH key (the 409 envelope is idempotency-cached under the old one);\n * - ONE attestation retry (client-side Turnstile failure or a server\n * `attestation_invalid`): `onAttestationInvalid` drops the cached token and\n * the executor re-runs under a fresh key.\n * Throws the final error otherwise.\n */\nexport async function runSubmission(\n rt: SubmitRuntime,\n executor: SubmitExecutor,\n hooks: { onAttestationInvalid?: () => void } = {},\n): Promise<SessionView> {\n let key = rt.uuid();\n let consentRecovered = false;\n let attestationRetried = false;\n let transportReplays = 0;\n for (;;) {\n try {\n return await executor(rt.client, key);\n } catch (err) {\n if (err instanceof AttestationError) {\n if (attestationRetried) throw err;\n attestationRetried = true;\n hooks.onAttestationInvalid?.();\n key = rt.uuid();\n continue;\n }\n if (!(err instanceof HostedApiError)) throw err;\n if (err.code === \"consent_required\" && !consentRecovered) {\n consentRecovered = true;\n await rt.ensureConsent({ force: true });\n key = rt.uuid();\n continue;\n }\n if (err.code === \"attestation_invalid\" && !attestationRetried) {\n attestationRetried = true;\n hooks.onAttestationInvalid?.();\n key = rt.uuid();\n continue;\n }\n if (TRANSPORT_CODES.has(err.code) && transportReplays < TRANSPORT_REPLAY_ATTEMPTS) {\n transportReplays += 1;\n const retryAfterMs =\n err.retryAfterSeconds !== undefined ? err.retryAfterSeconds * 1000 : undefined;\n await rt.sleep(\n retryAfterMs !== undefined\n ? Math.min(retryAfterMs, MAX_RETRY_AFTER_MS)\n : TRANSPORT_REPLAY_DELAY_MS,\n );\n continue;\n }\n throw err;\n }\n }\n}\n\nexport interface LivenessFlowDeps extends SubmitRuntime {\n artifacts: CapturedArtifacts;\n /** Fresh attestation per call (Turnstile execute, or `{sandbox: true}`). */\n getAttestation(): Promise<AttestationInput>;\n /** Latest hosted view (frontier decisions). */\n getView(): HostedSessionView;\n /** Re-GET the view from the server (after a RESYNC code) and fold it in. */\n refreshView(): Promise<HostedSessionView>;\n /** Fold a submit response into the view. */\n applySubmit(view: SessionView, submitted: CheckType): void;\n /** Flow-state hook for the capture handle. */\n onPhase(phase: \"uploading\" | \"checking\"): void;\n /** The verification was destroyed (the only thing that cancels a chain). */\n isCancelled(): boolean;\n}\n\nexport type LivenessFlowOutcome =\n | { kind: \"settled\" }\n | { kind: \"resync\" }\n | { kind: \"unavailable\"; reason: SdkFailureReason }\n | { kind: \"cancelled\" };\n\n/** Liveness states after which THIS capture's keyframe may feed face_match. */\nconst LIVENESS_CHAINABLE: ReadonlySet<string> = new Set([\"passed\", \"review\", \"processing\", \"skipped\"]);\n\n/**\n * Run the biometric submissions the CURRENT frontier allows, re-evaluating\n * after every submit and after every RESYNC re-GET:\n * - frontier `liveness` → clip upload + attestation + `liveness` (once per capture);\n * - frontier `face_match` → keyframe upload + `face_match` (once per capture),\n * allowed when liveness was NOT part of this capture (it had already\n * settled — any state, incl. `failed`: the selfie-fallback case, where this\n * capture only feeds face_match) or settled chainable from this capture\n * (passed / review / processing / skipped — or absent);\n * - anything else (terminal view, a non-biometric frontier, a check this\n * capture already submitted) → `settled` for the caller to map the view.\n * A RESYNC code re-GETs the view and re-enters (at most {@link RESYNC_REENTRY_MAX}\n * times); the final RESYNC surfaces as `resync` for the caller to map.\n */\nexport async function runLivenessFlow(deps: LivenessFlowDeps): Promise<LivenessFlowOutcome> {\n const { artifacts } = deps;\n const clip = artifacts.clip;\n const bareMime = stripMimeParams(artifacts.clipMime ?? \"\");\n if (\n artifacts.degraded === \"NO_RECORDER\" ||\n clip === undefined ||\n !CLIP_MIME_ALLOWLIST.includes(bareMime)\n ) {\n // No clip, or a container outside the presign allowlist (never sign an\n // off-allowlist type): v2 cannot run THIS capture.\n return { kind: \"unavailable\", reason: \"no_recorder\" };\n }\n\n // Per-capture executors: their closure caches (promoted media key,\n // attestation) survive a same-key transport replay; each is run at most once.\n const submitLiveness = buildLivenessSubmit(deps, clip, bareMime);\n const submitFaceMatch = buildFaceMatchSubmit(deps, artifacts.keyframe);\n let livenessSubmitted = false;\n let faceMatchSubmitted = false;\n let livenessChainable = true;\n let resyncs = 0;\n\n for (;;) {\n if (deps.isCancelled()) return { kind: \"cancelled\" };\n const view = deps.getView();\n if (isTerminalView(view)) return { kind: \"settled\" };\n const frontier = frontierStep(view)?.checkType;\n const next =\n frontier === \"liveness\" && !livenessSubmitted\n ? \"liveness\"\n : frontier === \"face_match\" && !faceMatchSubmitted && livenessChainable\n ? \"face_match\"\n : null;\n if (next === null) return { kind: \"settled\" };\n try {\n if (next === \"liveness\") {\n livenessSubmitted = true;\n const response = await submitLiveness();\n if (deps.isCancelled()) return { kind: \"cancelled\" };\n deps.applySubmit(response, \"liveness\");\n const state = stepOf(deps.getView(), \"liveness\")?.state;\n livenessChainable = state === undefined || LIVENESS_CHAINABLE.has(state);\n } else {\n faceMatchSubmitted = true;\n const response = await submitFaceMatch();\n if (deps.isCancelled()) return { kind: \"cancelled\" };\n deps.applySubmit(response, \"face_match\");\n }\n } catch (err) {\n if (deps.isCancelled()) return { kind: \"cancelled\" };\n const outcome = classifyFailure(err);\n if (outcome.kind !== \"resync\" || resyncs >= RESYNC_REENTRY_MAX) return outcome;\n resyncs += 1;\n // The server moved past what we hold: re-GET and re-enter from truth.\n await deps.refreshView();\n }\n }\n}\n\nfunction buildLivenessSubmit(\n deps: LivenessFlowDeps,\n clip: Blob,\n bareMime: string,\n): () => Promise<SessionView> {\n let clipKey: string | null = null;\n let attestation: AttestationInput | null = null;\n return () =>\n runSubmission(\n deps,\n async (client, idempotencyKey) => {\n if (clipKey === null) {\n deps.onPhase(\"uploading\");\n const presigned = await client.presign(\"clip\", bareMime, clip.size);\n await client.uploadToSpaces(presigned.uploadUrl, clip, bareMime);\n clipKey = (await client.completeMedia(presigned.key)).key;\n }\n // Attestation AFTER the upload settles (token stays fresh), cached\n // for the same-key replay.\n if (attestation === null) {\n try {\n attestation = await deps.getAttestation();\n } catch (err) {\n throw new AttestationError(err instanceof Error ? err.message : String(err));\n }\n }\n deps.onPhase(\"checking\");\n return client.submitCheck(\n \"liveness\",\n {\n clipKey,\n attestation,\n captureChannel: deps.artifacts.captureChannel,\n capture: captureInputOf(deps.artifacts),\n },\n { idempotencyKey },\n );\n },\n {\n onAttestationInvalid: () => {\n attestation = null;\n },\n },\n );\n}\n\n/**\n * The `input.capture` block of the liveness submission: the engine id, the\n * stream dimensions, the clip length and the face-capture timing from the\n * `captured` event — exactly the backend's capture-metadata allowlist\n * (identity-backend `capture-metadata.ts`). Never media, never tokens.\n */\nexport function captureInputOf(artifacts: CapturedArtifacts): LivenessCaptureInput {\n return {\n ...(artifacts.engine !== undefined ? { engine: artifacts.engine } : {}),\n width: artifacts.width,\n height: artifacts.height,\n ...(artifacts.clipMs !== undefined ? { clipMs: artifacts.clipMs } : {}),\n ...(artifacts.cameraReadyMs !== undefined ? { cameraReadyMs: artifacts.cameraReadyMs } : {}),\n ...(artifacts.faceAcquireMs !== undefined ? { faceAcquireMs: artifacts.faceAcquireMs } : {}),\n ...(artifacts.captureMs !== undefined ? { captureMs: artifacts.captureMs } : {}),\n };\n}\n\nfunction buildFaceMatchSubmit(deps: LivenessFlowDeps, keyframe: Blob): () => Promise<SessionView> {\n let imageKey: string | null = null;\n return () =>\n runSubmission(deps, async (client, idempotencyKey) => {\n if (imageKey === null) {\n deps.onPhase(\"uploading\");\n const presigned = await client.presign(\"image\", KEYFRAME_MIME, keyframe.size);\n await client.uploadToSpaces(presigned.uploadUrl, keyframe, KEYFRAME_MIME);\n imageKey = (await client.completeMedia(presigned.key)).key;\n }\n deps.onPhase(\"checking\");\n return client.submitCheck(\"face_match\", { imageKey }, { idempotencyKey });\n });\n}\n\nfunction classifyFailure(err: unknown): LivenessFlowOutcome {\n if (err instanceof AttestationError) return { kind: \"unavailable\", reason: \"attestation\" };\n if (err instanceof HostedApiError) {\n if (err.code === \"attestation_invalid\") return { kind: \"unavailable\", reason: \"attestation\" };\n if (RESYNC_CODES.has(err.code)) return { kind: \"resync\" };\n }\n return { kind: \"unavailable\", reason: \"network\" };\n}\n","// Cloudflare Turnstile: lazy script loader + execute-mode invisible challenge.\n// Ported from identity-hosted/lib/turnstile.ts (Plan 6 Task 10 AC4) — same\n// behaviour: the script loads ONCE, lazily, only when a liveness submit first\n// needs a token; each token request renders a fresh execute-mode widget,\n// resolves the callback token, and removes the widget. Hosts' CSP must allow\n// challenges.cloudflare.com.\n//\n// The site key is RUNTIME data (`HostedSessionView.turnstileSiteKey`); when it\n// is null the verification sends `{sandbox: true}` instead of calling this.\n\nexport const TURNSTILE_SCRIPT_SRC = \"https://challenges.cloudflare.com/turnstile/v0/api.js\";\n\n/** Bound on a single execute round-trip (challenge fetch + solve). */\nexport const TURNSTILE_EXECUTE_TIMEOUT_MS = 30_000;\n\nexport interface TurnstileRenderParams {\n sitekey: string;\n callback: (token: string) => void;\n \"error-callback\"?: (code?: string) => void;\n execution?: \"render\" | \"execute\";\n appearance?: \"always\" | \"execute\" | \"interaction-only\";\n}\n\n/** The subset of the Turnstile JS API this module uses. */\nexport interface TurnstileApi {\n render(container: HTMLElement, params: TurnstileRenderParams): string | undefined;\n execute(widgetId: string): void;\n remove(widgetId: string): void;\n}\n\n/** `window.turnstile` without augmenting the global Window type for consumers. */\nfunction installedApi(): TurnstileApi | undefined {\n return (window as unknown as { turnstile?: TurnstileApi }).turnstile;\n}\n\nlet loaderPromise: Promise<TurnstileApi> | null = null;\n\n/** Test-only: reset the module singleton between cases. */\nexport function resetTurnstileForTests(): void {\n loaderPromise = null;\n}\n\n/** Load the Turnstile script once (singleton; a load failure resets it so a\n * retry can re-attempt). */\nexport function loadTurnstile(): Promise<TurnstileApi> {\n loaderPromise ??= new Promise<TurnstileApi>((resolve, reject) => {\n const present = installedApi();\n if (present) {\n resolve(present);\n return;\n }\n const script = document.createElement(\"script\");\n script.src = TURNSTILE_SCRIPT_SRC;\n script.async = true;\n script.onload = () => {\n const api = installedApi();\n if (api) {\n resolve(api);\n } else {\n loaderPromise = null;\n reject(new Error(\"Turnstile script loaded but exposed no API\"));\n }\n };\n script.onerror = () => {\n loaderPromise = null;\n script.remove(); // a retry appends a fresh tag\n reject(new Error(\"Turnstile script failed to load\"));\n };\n document.head.appendChild(script);\n });\n return loaderPromise;\n}\n\n/** Options of {@link executeTurnstile}. */\nexport interface ExecuteTurnstileOptions {\n /** Bound on the challenge round-trip; default {@link TURNSTILE_EXECUTE_TIMEOUT_MS}. */\n timeoutMs?: number;\n /**\n * Aborts the challenge: the promise rejects and the widget, its container and\n * the timeout are cleaned up at once. `createVerification().destroy()` passes\n * its own signal so a torn-down verification never leaves a widget behind.\n */\n signal?: AbortSignal;\n}\n\nfunction abortError(): Error {\n return new Error(\"Turnstile challenge aborted\");\n}\n\n/**\n * Run one invisible execute-mode challenge and resolve its token. Rejects on\n * challenge error, on `signal` abort, or after {@link TURNSTILE_EXECUTE_TIMEOUT_MS}\n * — the verification maps a failure to `result {status:'unavailable',\n * reason:'attestation'}` after one retry.\n */\nexport async function executeTurnstile(\n siteKey: string,\n opts: ExecuteTurnstileOptions = {},\n): Promise<string> {\n const timeoutMs = opts.timeoutMs ?? TURNSTILE_EXECUTE_TIMEOUT_MS;\n const signal = opts.signal;\n if (signal?.aborted) throw abortError();\n const api = await loadTurnstile();\n if (signal?.aborted) throw abortError();\n const container = document.createElement(\"div\");\n document.body.appendChild(container);\n let widgetId: string | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n try {\n return await new Promise<string>((resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`Turnstile challenge timed out (${timeoutMs} ms)`)),\n timeoutMs,\n );\n if (signal !== undefined) {\n onAbort = () => reject(abortError());\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n widgetId = api.render(container, {\n sitekey: siteKey,\n execution: \"execute\",\n appearance: \"execute\",\n callback: resolve,\n \"error-callback\": (code) => {\n reject(new Error(`Turnstile challenge failed${code ? ` (${code})` : \"\"}`));\n },\n });\n if (widgetId === undefined) {\n reject(new Error(\"Turnstile render returned no widget id\"));\n return;\n }\n api.execute(widgetId);\n });\n } finally {\n // ONE cleanup path for every outcome (token / challenge error / timeout /\n // abort / render failure): the challenge timer and the abort listener must\n // never leak past settlement.\n clearTimeout(timer);\n if (signal !== undefined && onAbort !== undefined) signal.removeEventListener(\"abort\", onAbort);\n if (widgetId !== undefined) {\n try {\n api.remove(widgetId);\n } catch {\n // widget already gone — nothing to clean\n }\n }\n container.remove();\n }\n}\n","// createVerification — composes the hosted client, consent, the early registry\n// submit, the capture handle and the post-capture flow behind the public\n// contract (spec §3.1). Framework-free; the only state is the latest hosted\n// view (re-emitted on every update via `on('view')`).\nimport { HostedClient } from \"../client/hosted-client\";\nimport type { AttestationInput, CheckType, HostedSessionView, SessionView } from \"../client/types\";\nimport {\n createLivenessCaptureInternal,\n type LivenessCaptureInternalHandle,\n type LivenessCaptureInternals,\n} from \"../capture/create-liveness-capture\";\nimport { createEmitter } from \"./emitter\";\nimport {\n type HostedClientLike,\n runLivenessFlow,\n runSubmission,\n type SubmitRuntime,\n} from \"./liveness-flow\";\nimport {\n frontierStep,\n isTerminalView,\n mapSessionResult,\n mergeSessionView,\n stepOf,\n unavailable,\n} from \"./result\";\nimport { executeTurnstile } from \"./turnstile\";\nimport type {\n CapturedArtifacts,\n LivenessCaptureHandle,\n LivenessCaptureOptions,\n Verification,\n VerificationEvents,\n VerificationOptions,\n VerificationResult,\n} from \"./types\";\n\n/** Production API origin — the `apiOrigin` default. */\nexport const DEFAULT_API_ORIGIN = \"https://api.sekyro.ai\";\n\n/** Pause before the single `registry_unavailable` re-submit. */\nexport const REGISTRY_RETRY_DELAY_MS = 5_000;\n\n/** Bounded poll while a biometric verdict is still `processing` after the submits. */\nexport const PROCESSING_POLL_DELAY_MS = 1_500;\nexport const PROCESSING_POLL_MAX = 10;\n\n/**\n * Construction seams — tests and Sekyro's own hosted page (`@sekyro/web/internal`).\n * Not part of the partner contract.\n */\nexport interface VerificationInternals {\n /** Replace the HTTP client (default: `new HostedClient({...})`). */\n client?: HostedClientLike;\n /**\n * Replace the Turnstile challenge (default: {@link executeTurnstile}). The\n * `signal` aborts when the verification is destroyed (M1).\n */\n getTurnstileToken?: (siteKey: string, opts: { signal: AbortSignal }) => Promise<string>;\n /** Capture-engine seams forwarded to {@link createLivenessCaptureInternal}. */\n capture?: LivenessCaptureInternals;\n sleep?: (ms: number) => Promise<void>;\n uuid?: () => string;\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** `crypto.randomUUID()` with a v4 fallback for hosts that lack it. */\nexport function randomUuid(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c !== undefined && typeof c.randomUUID === \"function\") return c.randomUUID();\n const bytes = new Uint8Array(16);\n if (c !== undefined && typeof c.getRandomValues === \"function\") c.getRandomValues(bytes);\n else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;\n bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;\n const hex = [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Create a verification bound to one session (spec §3.1):\n * `consent()` → `startRegistry()` → `liveness({video})` → `on('result')`.\n */\nexport function createVerification(options: VerificationOptions): Verification {\n return createVerificationInternal(options, {});\n}\n\n/** {@link createVerification} with construction seams (tests / Sekyro-internal hosts). */\nexport function createVerificationInternal(\n options: VerificationOptions,\n internals: VerificationInternals,\n): Verification {\n const apiOrigin = options.apiOrigin ?? DEFAULT_API_ORIGIN;\n const autoRetry = options.autoRetry ?? true;\n const rearm = options.rearm ?? \"auto\";\n const client: HostedClientLike =\n internals.client ??\n new HostedClient({\n apiOrigin,\n sessionToken: options.clientToken,\n ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),\n });\n const sleep = internals.sleep ?? defaultSleep;\n const uuid = internals.uuid ?? randomUuid;\n const getTurnstileToken = internals.getTurnstileToken ?? executeTurnstile;\n const emitter = createEmitter<VerificationEvents>();\n\n let destroyed = false;\n // Aborts an in-flight Turnstile challenge on destroy() (widget + timer clean up).\n const lifetime = new AbortController();\n\n // ── view ───────────────────────────────────────────────────────────────────\n let view: HostedSessionView | null = null;\n let bootstrap: Promise<HostedSessionView> | null = null;\n const setView = (next: HostedSessionView): void => {\n view = next;\n emitter.emit(\"view\", next);\n };\n const ensureView = (): Promise<HostedSessionView> => {\n if (view !== null) return Promise.resolve(view);\n bootstrap ??= client\n .getSession()\n .then((next) => {\n setView(next);\n return next;\n })\n .finally(() => {\n bootstrap = null;\n });\n return bootstrap;\n };\n const refreshView = async (): Promise<HostedSessionView> => {\n const next = await client.getSession();\n setView(next);\n return next;\n };\n const applySubmit = (response: SessionView, submitted: CheckType): void => {\n if (view !== null) setView(mergeSessionView(view, response, submitted));\n };\n\n // ── consent (idempotent; `force` after a server consent_required) ──────────\n let consentInFlight: Promise<void> | null = null;\n const recordConsent = async (force: boolean): Promise<void> => {\n const current = await ensureView();\n if (!force && current.consentRecorded) return;\n await client.recordConsent(options.locale);\n if (view !== null) setView({ ...view, consentRecorded: true });\n };\n const ensureConsent = (opts?: { force?: boolean }): Promise<void> => {\n if (opts?.force === true) return recordConsent(true);\n consentInFlight ??= recordConsent(false).finally(() => {\n consentInFlight = null;\n });\n return consentInFlight;\n };\n\n const runtime: SubmitRuntime = {\n client,\n ensureConsent: (opts) => ensureConsent(opts),\n sleep,\n uuid,\n };\n\n // ── result emission ────────────────────────────────────────────────────────\n // A session verdict is emitted ONCE (the registry may settle it before any\n // capture); `retry` / `unavailable` are per capture.\n let terminalEmitted = false;\n const emitResult = (result: VerificationResult): void => {\n if (result.status === \"approved\" || result.status === \"rejected\" || result.status === \"review\") {\n if (terminalEmitted) return;\n terminalEmitted = true;\n }\n emitter.emit(\"result\", result);\n };\n\n // ── early registry ─────────────────────────────────────────────────────────\n let registry: Promise<void> | null = null;\n let registryFailed = false;\n // The frontier is population_registry on a session minted WITHOUT a bound\n // NNI: the SDK has nothing to submit (and must never guess one).\n let registryUnbound = false;\n const submitRegistry = (): Promise<SessionView> =>\n runSubmission(runtime, (c, idempotencyKey) =>\n // `{}` input: the session is bound to a SERVER-provided NNI (Plan A Task 2).\n c.submitCheck(\"population_registry\", {}, { idempotencyKey }),\n );\n const runRegistry = async (): Promise<void> => {\n try {\n const current = await ensureView();\n if (isTerminalView(current) || frontierStep(current)?.checkType !== \"population_registry\") {\n return;\n }\n if (current.nniBound !== true) {\n registryUnbound = true;\n return;\n }\n await ensureConsent();\n applySubmit(await submitRegistry(), \"population_registry\");\n const step = view === null ? undefined : stepOf(view, \"population_registry\");\n if (\n step?.state === \"input_required\" &&\n step.reason === \"registry_unavailable\" &&\n step.attemptsRemaining > 0\n ) {\n await sleep(REGISTRY_RETRY_DELAY_MS);\n if (destroyed) return;\n applySubmit(await submitRegistry(), \"population_registry\");\n }\n } catch {\n // Never throws (incl. 409 nni_locked / transport failures): the flow\n // surfaces it through result mapping.\n registryFailed = true;\n return;\n }\n if (view !== null && isTerminalView(view)) emitResult(mapSessionResult(view, { autoRetry }));\n };\n const startRegistry = (): Promise<void> => {\n registry ??= runRegistry();\n return registry;\n };\n\n // ── attestation ────────────────────────────────────────────────────────────\n // The SDK echoes the site key it rendered (`siteKey`) so the backend selects\n // the matching widget secret deterministically — Turnstile tokens are\n // single-use, so the verifier never retries with another secret (C1).\n const getAttestation = async (): Promise<AttestationInput> => {\n const current = await ensureView();\n const siteKey = current.turnstileSiteKey;\n if (siteKey === null) return { sandbox: true };\n return {\n channel: \"hosted_web\",\n siteKey,\n turnstileToken: await getTurnstileToken(siteKey, { signal: lifetime.signal }),\n };\n };\n\n // ── capture + flow ─────────────────────────────────────────────────────────\n // Submission chains are SERIALIZED: a new capture (incl. a host remount that\n // destroyed the old handle mid-upload) never abandons an in-flight chain — it\n // queues behind it and then evaluates the CURRENT frontier. Only destroy() of\n // the verification cancels. Handle side effects (flow states, auto re-arm)\n // reach the handle only while it is alive and still on the attempt that\n // produced the artifacts.\n interface BoundCapture {\n capture: LivenessCaptureInternalHandle;\n alive: boolean;\n /** Engine attempt counter (bumped on every `starting`). */\n attempt: number;\n }\n let active: BoundCapture | null = null;\n let chain: Promise<void> = Promise.resolve();\n let chainDepth = 0;\n\n const settleProcessing = async (): Promise<HostedSessionView> => {\n let current = await ensureView();\n for (\n let i = 0;\n i < PROCESSING_POLL_MAX &&\n !destroyed &&\n !isTerminalView(current) &&\n frontierStep(current) === undefined &&\n current.steps.some((step) => step.state === \"processing\");\n i += 1\n ) {\n await sleep(PROCESSING_POLL_DELAY_MS);\n if (destroyed) break;\n current = await refreshView();\n }\n return current;\n };\n\n const runFlow = async (\n bound: BoundCapture,\n artifacts: CapturedArtifacts,\n capturedAttempt: number,\n ): Promise<void> => {\n const { capture } = bound;\n const isCancelled = (): boolean => destroyed;\n // The handle still shows the attempt these artifacts came from (not\n // destroyed by a remount, no retry()/switchCamera() since).\n const ownsHandle = (): boolean => bound.alive && bound.attempt === capturedAttempt;\n const setPhase = (phase: \"uploading\" | \"checking\" | \"done\"): void => {\n if (ownsHandle()) capture.setFlowState(phase);\n };\n const finish = (result: VerificationResult): void => {\n if (isCancelled()) return;\n emitResult(result);\n if (!ownsHandle()) return;\n if (result.status === \"retry\" && rearm === \"auto\") {\n // Re-arm: the engine restarts and the next `captured` runs a new flow.\n capture.handle.retry();\n return;\n }\n // `done` (incl. a manual-rearm `retry`): the host calls `retry()` on the\n // handle when the user is ready — the engine restarts from there.\n capture.setFlowState(\"done\");\n };\n\n if (artifacts.degraded === \"NO_RECORDER\") {\n // No MediaRecorder: nothing is submitted (no liveness, no face_match).\n const sessionId = view?.sessionId ?? (await ensureView().catch(() => null))?.sessionId ?? \"\";\n finish(unavailable(sessionId, \"no_recorder\"));\n return;\n }\n\n setPhase(\"uploading\");\n try {\n // Registry first: it records consent itself only when it actually\n // submits — an unbound / failed registry frontier ends the flow below\n // without recording a consent nothing was submitted under.\n await startRegistry();\n if (isCancelled()) return;\n let current = await ensureView();\n if (!isTerminalView(current)) {\n if (frontierStep(current)?.checkType === \"population_registry\") {\n // The registry never passed (outage after the retry, or a request-\n // level failure): the biometric checks cannot be submitted.\n finish(\n unavailable(\n current.sessionId,\n registryUnbound ? \"nni_unbound\" : registryFailed ? \"network\" : \"registry_unavailable\",\n ),\n );\n return;\n }\n await ensureConsent();\n if (isCancelled()) return;\n const outcome = await runLivenessFlow({\n ...runtime,\n artifacts,\n getAttestation,\n getView: () => view ?? current,\n refreshView,\n applySubmit,\n onPhase: setPhase,\n isCancelled,\n });\n if (isCancelled()) return;\n if (outcome.kind === \"unavailable\") {\n finish(unavailable(current.sessionId, outcome.reason));\n return;\n }\n if (outcome.kind === \"resync\") await refreshView();\n current = await settleProcessing();\n }\n finish(mapSessionResult(current, { autoRetry }));\n } catch {\n if (isCancelled()) return;\n finish(unavailable(view?.sessionId ?? \"\", \"network\"));\n }\n };\n\n const enqueueFlow = (bound: BoundCapture, artifacts: CapturedArtifacts): void => {\n const capturedAttempt = bound.attempt;\n if (chainDepth > 0 && bound.alive) {\n // Queued behind an in-flight chain: the host sees `uploading` meanwhile.\n bound.capture.setFlowState(\"uploading\");\n }\n chainDepth += 1;\n chain = chain\n .then(() => runFlow(bound, artifacts, capturedAttempt))\n .finally(() => {\n chainDepth -= 1;\n })\n // runFlow never rejects (it maps failures to results); a rejection must\n // still never poison the chain for the captures queued after it.\n .catch(() => undefined);\n };\n\n const liveness = (captureOptions: LivenessCaptureOptions): LivenessCaptureHandle => {\n // Camera teardown of the previous handle (its chain, if any, survives).\n active?.capture.handle.destroy();\n if (active !== null) active.alive = false;\n const capture = createLivenessCaptureInternal(captureOptions, internals.capture);\n const bound: BoundCapture = { capture, alive: true, attempt: 0 };\n active = bound;\n capture.handle.on(\"state\", (state) => {\n // A fresh engine attempt: in-flight flows for older artifacts complete,\n // but no longer push states into (or re-arm) this handle.\n if (state === \"starting\") bound.attempt += 1;\n });\n capture.handle.on(\"captured\", (artifacts) => {\n // Partners' own `captured` handlers run first (the event fires BEFORE\n // any upload); the flow starts on the next microtask, after any\n // in-flight chain.\n void Promise.resolve().then(() => enqueueFlow(bound, artifacts));\n });\n const inner = capture.handle;\n return {\n get state() {\n return inner.state;\n },\n on: (event, handler) => inner.on(event, handler),\n start: () => inner.start(),\n retry: () => inner.retry(),\n switchCamera: () => inner.switchCamera(),\n destroy: () => {\n bound.alive = false;\n if (active === bound) active = null;\n inner.destroy();\n },\n };\n };\n\n return {\n consent: () => ensureConsent(),\n startRegistry,\n liveness,\n on: (event, handler) => emitter.on(event, handler),\n destroy: () => {\n destroyed = true;\n lifetime.abort();\n if (active !== null) {\n active.alive = false;\n active.capture.handle.destroy();\n active = null;\n }\n emitter.clear();\n },\n };\n}\n"],"mappings":"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","names":[]}
|