import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# `OctetVerdict`

The outcome of a predicate call. Returned by `isWithin`, `isOutside`, `contains` on `OctetSdk.loc`.

## Shape

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct OctetVerdict: Sendable {
    public let result: Result
    public let reason: ReasonCode
    public let message: String
    public let proof: LocationProof?
    public let validity: Interval?
    public let queriedAt: Date
    public let confidence: ConfidenceSummary
    public let achievableLevel: ProofLevel?   // non-nil only when reason == .insufficientPrecision

    public enum Result: Sendable, Hashable {
        case yes, no, indeterminate
    }

    public enum ReasonCode: Sendable, Hashable {
        case ok
        case noFix
        case futureTime
        case staleFix
        case noProofAtResolution
        case insufficientPrecision
        case attestationFailed
        case mockLocationDetected
        case sdkNotRunning
        case notYetReleased
    }
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
data class OctetVerdict(
    val result: Result,
    val reason: ReasonCode,
    val message: String,
    val proof: LocationProof?,
    val validity: Interval?,
    val queriedAt: Instant,
    val confidence: ConfidenceSummary,
    val achievableLevel: ProofLevel? = null,  // non-null only when reason == INSUFFICIENT_PRECISION
) {
    enum class Result { YES, NO, INDETERMINATE }

    enum class ReasonCode {
        OK, NO_FIX, FUTURE_TIME, STALE_FIX, NO_PROOF_AT_RESOLUTION,
        INSUFFICIENT_PRECISION, ATTESTATION_FAILED, MOCK_LOCATION_DETECTED,
        SDK_NOT_RUNNING, NOT_YET_RELEASED,
    }
}
```

  </TabItem>
</Tabs>

## Field invariants

- **`proof` is `nil`/`null` iff `result == .indeterminate`/`INDETERMINATE`.** Enforced as a precondition / `require` at construction. A `YES` or `NO` always carries a proof. An `INDETERMINATE` never does.
- **`validity` is non-nil exactly when `proof` is non-nil.** Both move together.
- **`queriedAt` is the `atTime` that was passed in.** Echoed back so callers do not have to thread it themselves.
- **`confidence` is always populated**, even for `INDETERMINATE` (with an empty summary in that case).
- **`achievableLevel` is non-nil only when `reason == .insufficientPrecision`.** It names the best [proof level](/docs/concepts/regions/) the SDK could reach for this query right now. New in 1.1. The SDK never silently down-levels to it on your behalf.

## `result`. The trichotomy.

| Value | Meaning |
|---|---|
| `.yes` / `YES` | Predicate holds. Proof attached. |
| `.no` / `NO` | Predicate provably does NOT hold. Proof attached. |
| `.indeterminate` / `INDETERMINATE` | SDK cannot answer. See `reason`. |

Never collapse `.indeterminate` to `.no` silently. See [Verdicts](/docs/concepts/verdicts/) for the rationale.

## `reason`. The `ReasonCode`.

| Code | Result | Meaning |
|---|---|---|
| `ok` / `OK` | `YES` or `NO` | Proof covered `atTime`. Predicate evaluated cleanly. |
| `noFix` / `NO_FIX` | `INDETERMINATE` | SDK started but no fix yet (also: running on simulator / emulator). |
| `futureTime` / `FUTURE_TIME` | `INDETERMINATE` | `atTime` is in the future beyond ±2 s clock-skew tolerance. |
| `staleFix` / `STALE_FIX` | `INDETERMINATE` | `atTime` falls outside the validity window of any cached proof. |
| `noProofAtResolution` / `NO_PROOF_AT_RESOLUTION` | `INDETERMINATE` | Cached proof is too coarse for the query (country proof, city query). |
| `insufficientPrecision` / `INSUFFICIENT_PRECISION` | `INDETERMINATE` | Conditions cannot support the requested precision. Read `achievableLevel`. |
| `attestationFailed` / `ATTESTATION_FAILED` | `INDETERMINATE` | Play Integrity / App Attest verdict not COMPLIANT. |
| `mockLocationDetected` / `MOCK_LOCATION_DETECTED` | `INDETERMINATE` | OS flagged mocked location. |
| `sdkNotRunning` / `SDK_NOT_RUNNING` | `INDETERMINATE` | `Octet.start(...)` was never called or session ended. |
| `notYetReleased` / `NOT_YET_RELEASED` | `INDETERMINATE` | Predicate path declared by the API but not wired in this SDK build. |

## `message`. For humans only.

`message` is a free-form string useful for log lines and debug UI. **Do not parse `message` for control flow.** Parse `reason` instead. The text is intentionally informal. The SDK reserves the right to refine it across versions.

## `proof`. The cryptographic witness.

`LocationProof` is the signed protobuf that a verifier checks against Octet's public keys. Its full shape is part of the protocol, not the SDK's public surface. See [Serialization](/docs/api-reference/serialization/) for the v1 alpha caveat about how `LocationProof` is currently rendered through `.toJson()`.

For most integrators the proof is opaque. Forward it to your verifier as-is.

## `validity`. The proof's temporal window.

The interval over which the underlying proof answers the predicate as stated. **Not** "how long the verdict will stay true". See [Time Semantics](/docs/concepts/time-semantics/).

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct Interval: Sendable, Hashable {
    public let from: Date
    public let to: Date
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
data class Interval(val from: Instant, val to: Instant)
```

  </TabItem>
</Tabs>

Inclusive-inclusive, `from <= to`.

## `confidence`. The score and flags.

`ConfidenceSummary` surfaces the SDK's overall confidence score and a list of flags (mock-location detected, VPN active, GNSS anomaly, and so on). A strict caller can treat a low-confidence `YES` as effectively `INDETERMINATE`:

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
let strictYes = verdict.result == .yes && verdict.confidence.overallScore > 0.8
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
val strictYes = verdict.result == OctetVerdict.Result.YES &&
                verdict.confidence.overallScore > 0.8
```

  </TabItem>
</Tabs>

The SDK never applies this collapse for you. The policy lives on the caller side.

## See also

- [Verdicts](/docs/concepts/verdicts/). Why the trichotomy matters.
- [Time Semantics](/docs/concepts/time-semantics/). How `validity` and `queriedAt` interact.
- [Serialization](/docs/api-reference/serialization/). `.toStr()` / `.toJson()` / `.toJsonl()` on verdicts.
