Provenance

Verification

Verification takes an asset (by attestation_id, image bytes, or video + manifest) and returns a Verification Result — the risk score, the factors that produced it, the chain of custody, and device info.

The result

{
  "attestation_id": "<uuid>",
  "claim_id": "<string | null>",
  "status": "verified | partial | failed",
  "risk_score": 0,
  "risk_factors": [ { "factor": "no_manifest", "points": 60, "description": "…" } ],
  "chain": [ { "event_type": "capture", "timestamp": "…", "actor": "device:…", "summary": "…" } ],
  "device_info": { "device_id": "…", "enrolled": true, "cert_valid": true, "cert_expires": "…" },
  "capture_type": "image",
  "liveness_checked": false,
  "synthetic_media_score": null,
  "synthetic_media_factors": null,
  "verified_at": "<RFC3339>",
  "sensor_hash": "<SHA-256 hex | null>"
}

sensor_hash echoes the signed bundle's hash: for an image, the SHA-256 of the raw bytes; for a video, the chunk-chain root. Use it to prove a file you hold is this asset before acting on it. The dashboard's Screen this asset does exactly that: it hashes the original you choose in the browser and only sends it to screening when the hash matches. It is null when there is no signed bundle (no_manifest).

Statuses

status is derived from risk_score (see risk scoring):

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

Flow

  1. Locate the manifest — extract the XMP bundle (image) or take the sidecar manifest (video). None found → no_manifest.
  2. Recompute the hash — image bytes → sensor_hash; video bytes → chunk chain.
  3. Verify the signature against device_cert, and the cert chain against the root CA.
  4. Check the registry — is the device enrolled? revoked?
  5. Score — apply the additive rules, cap at 100, derive the status.
  6. Screen if needed — an image with no_manifest and ?screen=true (sandbox default) also gets a synthetic-media score, kept as a separate field.

Two independent signals

risk_score answers "can we prove this is authentic?" synthetic_media_score answers "does an external detector think this looks AI-generated?" They are never merged. High confidence on the first makes the second irrelevant; the second only matters when the first can't vouch.