Provenance

Risk scoring

The risk score is a pure, rule-based function — additive, capped at 100. There is no ML anywhere in this path (the synthetic-media score is a separate, external signal). Given a bundle, the device record, and whether the image bytes matched, it returns a score and the factors that produced it.

Factors

| Factor | Points | Condition | | --- | --- | --- | | no_manifest | 60 | No attestation bundle found in the asset | | signature_invalid | 70 | Signature fails ECDSA verification | | sensor_hash_mismatch | 50 | Image bytes hash ≠ bundle sensor_hash | | device_revoked | 80 | Device in registry, marked revoked | | device_not_enrolled | 20 | Device not in registry | | cert_unknown_ca | 40 | Cert not signed by a known root CA | | timestamp_stale | 25 | capture_timestamp > 72h before claim submission | | gps_absent | 30 | gps null or lat/lng both 0 | | biometric_absent | 10 | biometric_present = false | | video_manifest_incomplete | 40 | Video: chunk indices non-contiguous, count mismatch, or missing fields | | frame_hash_mismatch | 50 | Video: recomputed chunk chain ≠ manifest (the video analogue of sensor-hash mismatch) | | session_nonce_mismatch | 70 | Live only: bundle nonce ≠ server nonce, or chain doesn't seed from it — the primary anti-replay check | | liveness_check_failed | 40 | Live only: secondary timing/continuity heuristics (not biometric liveness) | | hardware_backing_absent (native hardware SDK) | 40 | Enrolled device whose server-verified hardware_tier is software — real hardware that couldn't prove enclave backing. Sandbox simulated devices are exempt | | exif_timestamp_mismatch | 15 | JPEG EXIF DateTimeOriginal differs from the attestation time beyond tolerance (default 10 min, EXIF_TOLERANCE_SECONDS) | | exif_editor_trace | 15 | JPEG EXIF Software tag names an image editor (Photoshop, GIMP, Lightroom…) | | exif_missing | 5 | A JPEG capture with no EXIF metadata (stripped) — weak signal | | geofence_mismatch | 20 | Attestation GPS is farther than the geofence radius (default 5 km, GEOFENCE_RADIUS_KM) from the claim's stated loss_location | | cross_claim_link | 15 / 30 | This claim shares a device, location, or cloned cert with another of the carrier's claims. +15 for a weak link (GPS proximity), +30 for a strong one (shared device / cloned cert); the factor caps its own contribution at 30 |

The last four EXIF/geofence rows are advisory metadata signals: modest weights, all thresholds env-tunable, and they only fire when the input exists — EXIF factors apply to JPEGs only, and geofence never fires without a stated loss_location (a missing input is never penalized). Pass loss_location: { lat, lng } on attest to enable the geofence check.

Thresholds

| Score | Status | Action | | --- | --- | --- | | 0–30 | verified | Auto-approve | | 31–60 | partial | Adjuster review | | 61–80 | partial | Enhanced review | | 81–100 | failed | SIU escalation |

What to do at each factor

Carrier-configurable thresholds & weights

The 30/80 cuts and per-factor weights above are defaults, not law. A carrier can publish a risk policy (the claims_risk policy domain) that tunes them per its own risk appetite and jurisdiction — without any code change and without retroactively rescoring stored claims.

A risk policy has three optional parts:

{
  "thresholds": { "verified": 20, "partial": 60 },
  "factor_weights": { "carrier_policy_gps_missing": 40 },
  "rules": [
    { "id": "fl-water-gps",
      "when": [{ "fact": "claim_type", "op": "eq", "value": "water" },
               { "fact": "state", "op": "eq", "value": "FL" }],
      "require": { "signal": "gps" } }
  ]
}

Attest with claim_type / state so the rules can match. A carrier with no risk policy scores exactly as this page describes — bit-for-bit (the config is an optional argument to a pure scorer, regression-locked). Preview a draft against sample facts with POST /v1/policy/simulate?domain=claims_risk. Edit it in the dashboard's Policies → Claims risk tab.

The two-signal rule

risk_score and synthetic_media_score are never merged. An adjuster reads them together:

When the first signal is confident (a valid signature and matching hash), the second is irrelevant. The second only earns attention when the first can't vouch — e.g. a no_manifest legacy upload. Merging them into one number would blur a proof with a guess, which is exactly what Provenance refuses to do.