Skip to main content

Verdicts

In one sentence

A verdict is a trichotomy of YES, NO, or INDETERMINATE. Returning NO when the SDK cannot answer would be silent failure.

Why we need a third value

The temptation is to make isWithin(...) return a boolean. It would feel simpler. It would also lie. There are at least eight distinct reasons the SDK genuinely cannot answer a query:

  • The SDK started but has not seen a usable fix yet.
  • The caller asked about a moment in the future.
  • The caller asked about a moment too old to have a cached proof.
  • The cached proof is too coarse for the question (e.g. a country-level proof can't answer a city-level question).
  • The OS flagged the location as mocked.
  • App Attest / Play Integrity returned a non-compliant verdict.
  • The session ended.
  • The shape pairing is not wired in this SDK build yet.

Collapsing all of those into NO would erase the difference between answered no and unable to answer. An app that retries on NO would do the wrong thing. An app that ships a NO-as-proof to a verifier would ship a non-proof. The trichotomy makes the difference impossible to ignore.

The three values

  • YES. The predicate holds, the proof is attached.
  • NO. The predicate does not hold, and the SDK has proof of the negative claim. NO does not mean "I don't know."
  • INDETERMINATE. The SDK cannot answer right now. The reason field tells you why. The proof is nil.

Reason codes

ReasonCodeWhenResult
OKProof covers atTime; predicate evaluated cleanly.YES or NO
NO_FIXSDK started but no fix yet.INDETERMINATE
FUTURE_TIMEatTime is in the future beyond ±2 s clock-skew tolerance.INDETERMINATE
STALE_FIXatTime falls outside the validity window of any cached proof.INDETERMINATE
NO_PROOF_AT_RESOLUTIONCached proof is too coarse for the query (e.g. country-level proof vs. city-level question).INDETERMINATE
ATTESTATION_FAILEDPlay Integrity / App Attest verdict not COMPLIANT.INDETERMINATE
MOCK_LOCATION_DETECTEDOS flagged mocked location for this fix.INDETERMINATE
SDK_NOT_RUNNINGOctet.start(...) was never called or session ended.INDETERMINATE
NOT_YET_RELEASEDPredicate path declared in the API but not wired in this SDK build.INDETERMINATE

The SDK never surfaces the specific tamper signal that fired. Telling an attacker which sensor it watched would help them tune around it. The emulator / simulator hint is the one exception. That hint is a developer-environment indicator, not a security one.

Strict-boolean callers

If your business logic genuinely needs a boolean, collapse the verdict yourself. Be explicit about the policy:

// iOS
let ok = verdict.result == .yes && verdict.confidence.overallScore > 0.8
// Android
val ok = verdict.result == OctetVerdict.Result.YES &&
verdict.confidence.overallScore > 0.8

A low-confidence YES is a real proof. The SDK believes the claim. Some domains (KYC, sanctions, payments) need a tighter bar. The confidence score lives on the verdict so you can apply that bar. The SDK does not apply it for you.

Where to go next