core: Add liveness calibration profile model and analysis
The data side of guided liveness calibration: an accumulating profile of
labeled samples plus pure analysis over it. No hardware — a sample is a
named feature vector (the CheckEvaluation `detail` a check produces) with
a class label, so the caller supplies samples (CLI from V4L2, a future
GUI from frames it owns). This keeps the camera-ownership boundary clean:
core never opens a device.
- `SampleClass`: Live / PrintedPhoto / Screen / Other(label) — flexible
per-attack-type labeling for ISO-30107-3-style reporting.
- `LivenessSample`: { class, features: Vec<(name, value)>, timestamp }.
Stores computed features, not raw frames — privacy (no face images in
a non-HMAC file), size, and check-agnostic extensibility. Format
versioned so raw-frame retention could be added later.
- `LivenessProfile`: accumulate via `replace_class` (default re-capture)
or `append_batch` (--append), plus `clear_class`, `class_counts`,
`feature_names`. Carries a free-form `conditions` note since a profile
is only valid for similar capture conditions.
- Analysis: `FeatureStats`, `separation` (d-prime-like class separation
per feature), and `suggest_threshold` (sweeps cut points to minimise
live-reject + spoof-accept, orienting correctly whether live reads
higher or lower than the spoof). Suggestions only — never auto-applied.
Unit-tested: accumulation semantics, stats, separation magnitude, and
threshold suggestion on cleanly-separable / overlapping / inverted-sense
data.