bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
318 lines
8.9 KiB
Markdown
318 lines
8.9 KiB
Markdown
# quiXzoom — Evidence Graph & Verified Claim
|
||
*Beslutad: 2026-06-17. Ägare: Erik Svensson.*
|
||
*Bygger på: QUIXZOOM_VISION.md, QUIXZOOM_VERIFIED_REALITY.md, QUIXZOOM_AVO_MASTERPROMPT_V2.md*
|
||
|
||
---
|
||
|
||
## Enheten är utsagan — inte bilden, inte evidenspaketet
|
||
|
||
Det ni säljer, lagrar, fakturerar och kan bli stämda över är **påståendet**:
|
||
|
||
> "Livbojen vid kaj 14B existerade och var i korrekt skick 2026-06-17 kl 12:22 UTC."
|
||
|
||
Det är en `verified_claim`. Allt annat — frames, OCR, gyrodata, liveness — är evidens *för* utsagan.
|
||
|
||
---
|
||
|
||
## Claim-schemat
|
||
|
||
```json
|
||
{
|
||
"claim_id": "clm_9f3a8c1b",
|
||
"claim_type": "object_existence_and_condition",
|
||
"claim_text": "Livbojen vid kaj 14B existerar och är i korrekt skick.",
|
||
"control_point": "lifebuoy_kaj14b",
|
||
"knowledge_pack_id": "maritime_safety_v2",
|
||
|
||
"trust_threshold": 0.92,
|
||
"trust_achieved": 0.961,
|
||
"decision": "verified",
|
||
|
||
"evidence_bundle_id": "evb_7d2f1a9e",
|
||
"evidence_bundle_hash": "sha256:a3f8b2...",
|
||
|
||
"zoomer_id": "z_9981",
|
||
"device_id": "dev_ios_xyz",
|
||
"session_id": "sess_abc123",
|
||
|
||
"geo": { "lat": 59.334, "lng": 18.063, "accuracy_m": 4 },
|
||
"timestamp_start": "2026-06-17T12:20:41Z",
|
||
"timestamp_decision": "2026-06-17T12:22:04Z",
|
||
|
||
"signature": "ed25519:9f3a...",
|
||
"immutable": true
|
||
}
|
||
```
|
||
|
||
`trust_threshold` ägs av kunskapspaketet — per claim-typ, aldrig globalt.
|
||
`decision` är append-only. Korrektion sker som ny claim som refererar bakåt.
|
||
|
||
---
|
||
|
||
## Sufficiency-funktionen — förtroende-ackumulator
|
||
|
||
Systemet samlar evidens tills `trust_achieved ≥ trust_threshold`. Sedan stannar det.
|
||
|
||
```
|
||
trust_achieved = Σ (weight_i × confidence_i × liveness_factor)
|
||
```
|
||
|
||
Varje evidenselement bidrar med viktad konfidens:
|
||
|
||
| Element | Vikt (example) | Bidrar till |
|
||
|---------|---------------|-------------|
|
||
| Visual coverage frame | 0.15 | Objekt existerar |
|
||
| OCR serial match | 0.25 | Objekt identifierat |
|
||
| Liveness Score >90 | 0.20 | Observation äkta |
|
||
| GPS match ±15m | 0.15 | Objekt på rätt plats |
|
||
| App Attest valid | 0.10 | Enhet omodifierad |
|
||
| Challenge passed (per st) | 0.08 | Anti-spoofing |
|
||
| Reference match | 0.07 | Korrekt skick |
|
||
|
||
**Vikterna och trösklarna ägs av kunskapspaketet**, inte av AVO-koden.
|
||
|
||
### Konsekvensen
|
||
|
||
Ibland räcker 4 frames och 2 challenges. Ibland krävs 40 frames och 5 challenges.
|
||
Systemet vet när det är klart — Zoomern vet det aldrig förrän det är grönt.
|
||
|
||
### Sufficiency kan misslyckas
|
||
|
||
Om systemet inte når tröskeln inom:
|
||
- Max scan-tid (konfigurerbart, default 120s)
|
||
- Max challenges (default 5)
|
||
- Max frames (default 600)
|
||
|
||
→ `decision: insufficient_evidence` → eskalera till manuell granskning.
|
||
|
||
**Aldrig:** tvinga fram ett svagt godkännande för att undvika eskalering.
|
||
|
||
```json
|
||
{
|
||
"claim_id": "clm_4b2e9d7a",
|
||
"decision": "insufficient_evidence",
|
||
"trust_achieved": 0.71,
|
||
"trust_threshold": 0.92,
|
||
"limiting_factor": "liveness_score_too_low",
|
||
"escalation": "manual_review",
|
||
"photographer_directive": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Evidence Bundle — schemat
|
||
|
||
Det fullständiga, signerade paketet som claim:en refererar till:
|
||
|
||
```json
|
||
{
|
||
"bundle_id": "evb_7d2f1a9e",
|
||
"claim_id": "clm_9f3a8c1b",
|
||
"created_at": "2026-06-17T12:22:04Z",
|
||
|
||
"liveness": {
|
||
"score": 97,
|
||
"signals": {
|
||
"gyro_natural": true,
|
||
"parallax_verified": true,
|
||
"focus_changes": 4,
|
||
"gps_match": true,
|
||
"app_attest": true,
|
||
"stream_continuous": true,
|
||
"challenges_issued": 3,
|
||
"challenges_passed": 3
|
||
}
|
||
},
|
||
|
||
"evidence_elements": [
|
||
{
|
||
"element_id": "e1",
|
||
"type": "visual_frame",
|
||
"frame_index": 142,
|
||
"timestamp_ms": 3820,
|
||
"confidence": 0.97,
|
||
"weight": 0.15,
|
||
"contribution": 0.1455,
|
||
"hash": "sha256:f1a2b3..."
|
||
},
|
||
{
|
||
"element_id": "e2",
|
||
"type": "ocr_extraction",
|
||
"frame_index": 198,
|
||
"timestamp_ms": 5180,
|
||
"confidence": 0.94,
|
||
"weight": 0.25,
|
||
"contribution": 0.2350,
|
||
"extracted_value": "SE-4821-9938-01",
|
||
"hash": "sha256:c4d5e6..."
|
||
},
|
||
{
|
||
"element_id": "e3",
|
||
"type": "challenge_response",
|
||
"challenge_issued_at_ms": 6200,
|
||
"challenge_directive": "Gå ett steg bakåt",
|
||
"challenge_class": "liveness",
|
||
"response_detected_at_ms": 8100,
|
||
"confidence": 0.96,
|
||
"weight": 0.08,
|
||
"contribution": 0.0768
|
||
}
|
||
],
|
||
|
||
"trust_accumulation": [
|
||
{ "after_element": "e1", "trust_so_far": 0.1455 },
|
||
{ "after_element": "e2", "trust_so_far": 0.3805 },
|
||
{ "after_element": "e3", "trust_so_far": 0.4573 }
|
||
],
|
||
|
||
"bundle_hash": "sha256:a3f8b2...",
|
||
"signature": "ed25519:9f3a...",
|
||
"immutable": true
|
||
}
|
||
```
|
||
|
||
`trust_accumulation` loggar hur förtroendet byggdes upp steg för steg.
|
||
Vid återgranskning kan man se exakt varför beslutet fattades — och vid vilken evidens tröskeln passerades.
|
||
|
||
---
|
||
|
||
## Challenge-direktivets vokabulär
|
||
|
||
**Designkrav:** Zoomern kan aldrig avgöra om ett direktiv är guidning eller liveness-challenge.
|
||
|
||
```
|
||
"Flytta kameran 30 cm åt vänster." ← kan vara guidning (framing_ok)
|
||
eller challenge (liveness, kräver parallax)
|
||
|
||
"Höj kameran lite." ← kan vara guidning (angle_ok)
|
||
eller challenge (liveness, kräver rörelse)
|
||
|
||
"Visa objektets nedre del." ← kan vara guidning (coverage)
|
||
eller challenge (liveness, kräver ny vinkel)
|
||
```
|
||
|
||
Samma fras, samma röst, samma UI. Challenge-klassen är intern — aldrig exponerad.
|
||
|
||
```json
|
||
{
|
||
"directive": "Gå ett steg bakåt",
|
||
"directive_class": "liveness_challenge", ← INTERN, visas aldrig för Zoomer
|
||
"expected_signal": "zoom_out + parallax_change",
|
||
"timeout_ms": 8000
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Korrektion av en verified_claim
|
||
|
||
En verifierad utsaga är immutable. Korrektion sker som ny claim som refererar bakåt:
|
||
|
||
```json
|
||
{
|
||
"claim_id": "clm_4c7f2b8d",
|
||
"claim_type": "correction",
|
||
"corrects_claim_id": "clm_9f3a8c1b",
|
||
"correction_reason": "Manuell granskning: serienummer OCR-fel, korrekt värde SE-4821-9938-02",
|
||
"corrected_by": "reviewer_human_012",
|
||
"timestamp": "2026-06-17T15:33:11Z",
|
||
"signature": "ed25519:7b2c...",
|
||
"immutable": true
|
||
}
|
||
```
|
||
|
||
Originalclaim:en finns kvar. Granskningskedjan är komplett.
|
||
|
||
---
|
||
|
||
## Kunskapspaketet äger tröskeln
|
||
|
||
```json
|
||
{
|
||
"object": "lifebuoy",
|
||
"claim_type": "existence_and_condition",
|
||
"trust_threshold": 0.92,
|
||
"evidence_weights": {
|
||
"visual_frame": 0.15,
|
||
"ocr_serial": 0.25,
|
||
"liveness_score_component": 0.20,
|
||
"gps_match": 0.15,
|
||
"app_attest": 0.10,
|
||
"challenge_response": 0.08,
|
||
"reference_match": 0.07
|
||
},
|
||
"min_liveness_score": 80,
|
||
"max_scan_seconds": 120,
|
||
"compliance": "maritime_safety"
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"object": "passport",
|
||
"claim_type": "identity_document_authenticity",
|
||
"trust_threshold": 0.97,
|
||
"evidence_weights": {
|
||
"mrz_validation": 0.30,
|
||
"photo_match": 0.25,
|
||
"tamper_detection": 0.20,
|
||
"liveness_score_component": 0.15,
|
||
"challenge_response": 0.10
|
||
},
|
||
"min_liveness_score": 95,
|
||
"max_scan_seconds": 60,
|
||
"compliance": "regulated"
|
||
}
|
||
```
|
||
|
||
Passet kräver 97% förtroende och minst 95 i liveness.
|
||
Livbojen kräver 92% och minst 80 i liveness.
|
||
Samma motor — olika parametrar. Aldrig hårdkodad logik.
|
||
|
||
---
|
||
|
||
## Persistens och juridisk hållbarhet
|
||
|
||
| Krav | Implementation |
|
||
|------|---------------|
|
||
| Immutability | Append-only storage (S3 Object Lock / DynamoDB Streams) |
|
||
| Signering | Ed25519 per claim och per bundle |
|
||
| Återgranskningsbarhet | `trust_accumulation`-log i varje bundle |
|
||
| Temporal binding | Timestamp signerat med bundle-hash — kan inte backdateras |
|
||
| Separation | Bundle sparas separat från claim — kan granskas oberoende |
|
||
|
||
**Samma mönster som ledger:** en post ändras aldrig. Korrektion = ny post med bakåtreferens.
|
||
|
||
---
|
||
|
||
## Vad AVO v2-kontraktet fortfarande styr
|
||
|
||
Evidence Graph och Verified Claim är ett lager *ovanpå* AVO v2 — inte en ersättning.
|
||
|
||
```
|
||
Smart Scan (insamling)
|
||
↓
|
||
AVO Fas A — Liveguidning (on-device, per frame)
|
||
↓
|
||
AVO Fas B — Slutanalys (server, per exponering)
|
||
↓
|
||
Sufficiency-funktion (ackumulerar trust mot claim-tröskel)
|
||
↓
|
||
Evidence Bundle (signerat paket)
|
||
↓
|
||
Verified Claim (persisterad, immutable utsaga)
|
||
↓
|
||
QuickSum (summary till uppdragsgivare)
|
||
```
|
||
|
||
---
|
||
|
||
## Öppna frågor
|
||
|
||
1. **Vem äger kunskapspaketen?** quiXzoom sätter defaults — kan uppdragsgivare överskriva? Sätt ett golv.
|
||
2. **Juridisk beviskraft** — ed25519-signatur + immutable storage: räcker det i EU-rätt för försäkringsärenden? Behöver vi en ackrediterad tidsstämpel (RFC 3161)?
|
||
3. **RFC 3161 trusted timestamp** — binds claim-hashen till en extern tidsstämpelserver ger oberoende temporalt bevis. Troligtvis nödvändigt för regulated-klassen.
|
||
4. **Liveness-score för offline** — App Attest kräver nät. Offline-scanning ger lägre liveness-score. Accepteras av knowledge_pack?
|
||
5. **Granskningsgränssnitt** — hur ser det ut när en försäkringshandläggare öppnar ett bundle 6 månader senare? UI behöver designas för människa, inte för AVO.
|