# Decision Response

This page describes what the Octet policy engine returns for a decision, and the signed receipt that carries it to your backend.

## DecisionResult

`decide` in the Magistrate module returns a `DecisionResult`:

| Field | Type (Swift / Kotlin) | Description |
|---|---|---|
| `decision` | `String` | `permit` or `deny` |
| `cause` | `String?` | Set when the location couldn't be settled, which is UNKNOWN. See [Cause](#cause). `nil` or `null` when the location was decided |
| `env` | `String` | `prod`, or `sandbox` for a simulator, emulator or debug build |
| `isPermit` | `Bool` / `Boolean` | `true` only when `decision` is `permit` |
| `obligations` | `[PolicyObligation]` / `List<PolicyObligation>` | Conditions attached to a `permit`. See [Obligations](#obligations) |
| `reasons` | `[String]` / `List<String>` | Why the engine decided as it did, in plain words. For logs and support, not for program logic |
| `policyVersion` | `UInt64` / `ULong` | The version of the policy that decided |
| `proofRoot` | `String` | A commitment to the proofs the decision was made on |
| `decisionRef` | `String` | The reference of this check |
| `receipt` | `String?` | The signed receipt. Send it to your backend |
| `claimedArea` | optional | The country or state the device disclosed in its proof. Never coordinates |

`decideMany` returns a `DecisionSet` with the shared `decisionRef` and `claimedArea`, and one result per policy in `results`. Each carries its `policy` name, its own `cause`, `env` and `receipt`.

## Receipt claims

The receipt is a compact JWS. Its header is `{"alg": "EdDSA", "typ": "octet-decision-receipt+jwt", "kid": "<key id>"}`. Its claims:

| Claim | Description |
|---|---|
| `iss` | Always `octet-policy-engine` |
| `iat`, `exp` | Issued at and expiry, Unix seconds. `exp` is 300 seconds after `iat` |
| `decision` | `permit` or `deny` |
| `cause` | Present only when the decision came from `on_indeterminate`. See [Cause](#cause) |
| `obligations` | The obligations attached to the decision |
| `policy`, `policy_version` | The policy and the version that decided |
| `decision_ref` | The reference of the check this receipt answers |
| `license_id`, `account_id` | Your license, and the account you passed to `decide`, if any |
| `proof_root` | A commitment to the proofs the decision was made on |
| `seq` | The engine's sequence number for this decision |
| `check_policies` | For `decideMany`, every policy in the check |
| `env` | `prod`, or `sandbox` for a simulator, emulator or debug build. A receipt without `env` is `prod` |

[Verify and Enforce](/docs/magistrate/get-started/verify-and-enforce/) shows how to check a receipt.

## YES, NO and UNKNOWN

The studio shows each policy as a question with three answers. The engine returns `permit` or `deny`. Read a result, or a verified receipt, in this order:

1. If `cause` is present, the answer is **UNKNOWN**.
2. Otherwise, if `decision` equals the policy's `yes_is`, the answer is **YES**.
3. Otherwise, the answer is **NO**.

`yes_is` depends on how the question is asked. For "Is the device in Austria?" it is `permit`. For "Is the device in Iran?" it is `deny`. The studio's Ship page shows your policy's question and which decision is YES. [`GET /v1/policies`](/docs/magistrate/reference/endpoints/#policies) lists `question` and `yes_is` for Octet's demo policies.

## Cause

`cause` says why the location couldn't be settled:

| Cause | Meaning |
|---|---|
| `absence` | No proof for something the policy needs. This includes a device whose location the SDK didn't trust, because the SDK then sends no proof |
| `stale` | The proofs were older than the policy's `freshness` |
| `authenticity` | A proof didn't verify |
| `unbound` | A proof wasn't bound to this check's challenge |
| `inconsistent` | A proof contradicts the engine's own check, or the proofs disagree on the country |
| `undecidable` | The proofs are valid but can't settle a rule, for example a country-level proof for a state rule |
| `other` | Something other than a region couldn't be settled, such as a `requires` threshold |

## Obligations

A `permit` can carry obligations. A permit with obligations counts only once your app and backend have fulfilled every one. The Magistrate module returns them as they are and never acts on them.

| Swift | Kotlin | In the receipt | Means |
|---|---|---|---|
| `.stepUpAuth` | `StepUpAuth` | `"StepUpAuth"` | Authenticate the user again before going ahead |
| `.log(channel:)` | `Log` | `{"Log": {"channel": "audit"}}` | Record the decision in the named log |
| `.degrade(capability:)` | `Degrade` | `{"Degrade": {"capability": "hd_video"}}` | Go ahead without the named capability |
| `.ttl(durationMs:)` | `Ttl` | `{"Ttl": {"duration": {"ms": 300000}}}` | The permit lasts only this long |
| `.custom(name:args:)` | `Custom` | `{"Custom": {"name": "x-...", "args": []}}` | A rule of your own, named in the policy |
| `.unknown(rawJson:)` | `Unknown` | anything else | Treat it as not fulfilled |

Policies from the Magistrate studio don't attach obligations yet. Handle them anyway, so a later policy version can't widen what a permit allows.
