Verdicts
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.NOdoes not mean "I don't know."INDETERMINATE. The SDK cannot answer right now. Thereasonfield tells you why. Theproofisnil.
Reason codes
ReasonCode | When | Result |
|---|---|---|
OK | Proof covers atTime; predicate evaluated cleanly. | YES or NO |
NO_FIX | SDK started but no fix yet. | INDETERMINATE |
FUTURE_TIME | atTime is in the future beyond ±2 s clock-skew tolerance. | INDETERMINATE |
STALE_FIX | atTime falls outside the validity window of any cached proof. | INDETERMINATE |
NO_PROOF_AT_RESOLUTION | Cached proof is too coarse for the query (e.g. country-level proof vs. city-level question). | INDETERMINATE |
ATTESTATION_FAILED | Play Integrity / App Attest verdict not COMPLIANT. | INDETERMINATE |
MOCK_LOCATION_DETECTED | OS flagged mocked location for this fix. | INDETERMINATE |
SDK_NOT_RUNNING | Octet.start(...) was never called or session ended. | INDETERMINATE |
NOT_YET_RELEASED | Predicate 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
- Time Semantics for how
atTime,validity, and the staleness window interact. - Regions for why
NO_PROOF_AT_RESOLUTIONcan fire even when you "have" a proof. - OctetVerdict API Reference for the exact field types.