> ## Documentation Index
> Fetch the complete documentation index at: https://omniflagsdoc.omniretail.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Evaluation

> The complete algorithm the SDK uses to resolve a flag value

Flag evaluation is **entirely local**: it runs inside the SDK against a cached snapshot. There is no per-evaluation network call. The network is only used to periodically refresh the snapshot in the background.

***

## Evaluation algorithm

When you call `useFlag`, `useFlagValue`, `client.IsEnabled`, or any other evaluation method, the SDK runs the following steps in order:

```
1. Flag not in snapshot?
   → Return caller default     (reason: ERROR, code: FLAG_NOT_FOUND)

2. Wrong type requested?
   → Return caller default     (reason: ERROR, code: TYPE_MISMATCH)

3. Flag disabled?
   → Return flag default       (reason: STATIC)

4. Walk targeting rules top → bottom:
   a. All conditions match?
      → Return rule's serve value   (reason: TARGETING_MATCH, ruleId: <id>)

5. Flag-level rollout configured?
   → Deterministically bucket user
   → Boolean flag: return true/false   (reason: ROLLOUT)
   → Multi-value:  return split value  (reason: SPLIT)

6. No match
   → Return flag default       (reason: DEFAULT)
```

Steps 1–3 are guard checks. Steps 4–6 are the evaluation proper.

***

## Reason codes

Every evaluation produces a reason code. Access it via the `*Detail` / `useFlagVariant` APIs.

| Reason            | When it occurs                                                            |
| ----------------- | ------------------------------------------------------------------------- |
| `STATIC`          | Flag is disabled; returns the flag's configured default value             |
| `DEFAULT`         | Flag is enabled, no rule matched, no rollout is configured                |
| `TARGETING_MATCH` | A targeting rule matched; the rule's serve value is returned              |
| `SPLIT`           | User was bucketed into a multi-value traffic split                        |
| `ROLLOUT`         | User was bucketed into a boolean rollout                                  |
| `ERROR`           | Evaluation could not complete; check `errorCode` for the specific failure |

***

## Error codes

Error codes appear on results with reason `ERROR`.

| Code                    | Cause                                                                                              | SDK behaviour                           |
| ----------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `FLAG_NOT_FOUND`        | Flag key not present in the snapshot (flag disabled, archived, or wrong environment)               | Returns the **caller-supplied default** |
| `TYPE_MISMATCH`         | The flag exists but its type doesn't match the method called (e.g. `GetString` on a `number` flag) | Returns the **caller-supplied default** |
| `MISSING_TARGETING_KEY` | Rollout or traffic split requires a bucketing key, but the evaluation context contains none        | Returns the **flag's default value**    |
| `PROVIDER_NOT_READY`    | SDK has not yet loaded its first snapshot                                                          | Returns the **caller-supplied default** |

<Note>
  `FLAG_NOT_FOUND` is the normal outcome when a flag is disabled. Because disabled flags are excluded from the snapshot, the SDK cannot distinguish between "flag disabled" and "flag doesn't exist". Both appear as `FLAG_NOT_FOUND`. For boolean flags this is always transparent, since both `FLAG_NOT_FOUND` and a disabled flag return `false`.
</Note>

***

## Deterministic bucketing

Rollout and traffic splits use a **FNV-1a (32-bit)** hash to assign each user a bucket position between 0 and 100,000. The hash input is:

```
{flagKey} + {envKey} + {targetingKey} + {salt}
```

The `targetingKey` is resolved from the evaluation context using the first available value in this priority order:

```
customerId → agentId → businessId → businessBranchId → walletId → customerRef → omnipayId
```

Because the hash is deterministic, the same user always lands in the same bucket for the same flag, across all SDK platforms, restarts, and devices. A user who receives `true` for a flag at 10% rollout will continue to receive `true` as that rollout expands to 20%, 50%, and 100%.

If none of the bucketing keys are present in the context, rollout-based evaluation returns `MISSING_TARGETING_KEY` and falls back to the flag's default value.

***

## Snapshot caching

| Platform     | Storage        | Warm start                                                                    |
| ------------ | -------------- | ----------------------------------------------------------------------------- |
| React        | In-memory      | No — `isLoading` is `true` until the first fetch completes                    |
| React Native | `AsyncStorage` | Yes — flags available immediately from the persisted cache                    |
| .NET         | In-memory      | No — the hosted service loads the snapshot at startup, before requests arrive |

All SDKs refresh the snapshot on a polling interval configured server-side (default: 2 minutes). Refreshes use HTTP ETags. If the snapshot hasn't changed, the CDN returns `304 Not Modified` and no data is transferred. React Native additionally refreshes on foreground resume.

***

## Stale snapshot behaviour

If the SDK cannot reach the CDN during a refresh attempt (network error, timeout, CDN outage), it continues using the last successfully fetched snapshot. Evaluation is uninterrupted. The client status reflects the failure:

* `isFetching` returns to `false`
* `error` is set to the fetch error
* `origin` remains `'CACHE'` or `'SERVER'` (the source of the snapshot currently in use)

The SDK will retry on the next polling interval. As long as a snapshot has been loaded at least once, there is no degradation in flag evaluation capability during outages.
