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

# Kotlin SDK - Caching Behaviour

> TTL sync, ETag/304, quarantine/billing pauses in the Kotlin SDK.

The SDK maintains an in-memory snapshot of all config entries, secrets, and feature flags. This snapshot is the source of truth for all getter calls. The SDK never makes a blocking network call inside a getter.

## TTL-based background sync

Every getter (`getString`, `getBoolean`, `getInt`, `getDouble`, `getJson`, `getFlag`, `getSecret`, `getSecretFilePath`) calls `ensureFresh()` before reading the snapshot.

`ensureFresh()` logic:

1. If the billing-overdue flag is set, return immediately (no sync).
2. If a quarantine is active (`now < quarantineUntil`), return immediately (no sync).
3. If `now - snapshot.syncedAt < ttl`, the snapshot is fresh - return immediately.
4. Otherwise, launch a background coroutine that calls `sync()`. The getter returns the current (stale) snapshot value without waiting.

This means:

* Getters are always non-blocking.
* The first call after TTL expiry returns a slightly stale value while the refresh runs in the background.
* The second call (after the background sync completes) returns the fresh value.

```
time 0:   create() -> sync() -> snapshot stamped
time 60s: getString("x") -> ensureFresh() -> stale -> launch background sync
                         -> returns old value immediately
time 60s + ~200ms: background sync completes -> snapshot updated
time 60s + 300ms: getString("x") -> ensureFresh() -> fresh -> returns new value
```

## ETag / 304 support

Every `sync()` call sends the last received `ETag` in an `If-None-Match` header. When the server data has not changed since the last sync, the server returns HTTP 304 Not Modified with an empty body. The SDK skips deserialization and keeps the existing snapshot. This reduces both bandwidth and CPU usage on polling-heavy deployments.

## Stream path bypasses TTL

When `connectStream()` is active and the server pushes an event, `sync()` is called immediately - the TTL is ignored. This means the snapshot is updated within milliseconds of a change on the platform, regardless of the configured `ttl` value.

For production services with the stream active, a large `ttl` (120 seconds or more) is fine. The stream handles freshness; the TTL is only a safety net for when the stream is in fallback mode.

## Quarantine pause

When `sync()` receives HTTP 429 with a quarantine body, the SDK:

1. Sets `quarantineUntil` to the `expiresAt` timestamp from the response.
2. Calls `observer.onQuarantined(reason, expiresAt)`.
3. Throws `NexusQuarantinedException`.

While `now < quarantineUntil`, `ensureFresh()` skips the sync check entirely. Getters continue to return the last cached values. No sync requests are sent to the server.

After `quarantineUntil` passes, `ensureFresh()` resumes normal TTL checks and the next stale getter call triggers a fresh sync.

## Billing-overdue pause

When `sync()` receives HTTP 402, the SDK:

1. Sets the internal `billingOverdue` flag to `true`.
2. Calls `observer.onBillingOverdue()`.
3. Throws `NexusBillingException`.

While `billingOverdue` is `true`, `ensureFresh()` returns immediately without launching any background sync. Getters return the last successfully cached values indefinitely.

The flag is cleared on the next successful sync.

## Manual sync

You can trigger a sync at any time:

```kotlin theme={null}
// Force an immediate sync (suspends until complete)
client.sync()
```

## Snapshot atomicity

The snapshot is stored in an `AtomicReference<Snapshot>`. Reads and writes are lock-free. A new `Snapshot` object is constructed from the full sync response and swapped in atomically. There is no partial-update window where a getter could read a mix of old and new values.

## File secrets

Secrets of type `file` are materialised to a temporary directory at sync time. The file path is stored in `fileSecretPaths` and returned by `getSecretFilePath(key)`. Files are updated on each sync when their value changes. All temp files are deleted when `close()` is called.

## Summary

| Event                    | Effect on snapshot                                                              |
| ------------------------ | ------------------------------------------------------------------------------- |
| `create()`               | Blocking sync; throws on failure                                                |
| TTL expiry + getter call | Background sync launched; stale value returned immediately                      |
| SSE event received       | Immediate background `sync()` regardless of TTL                                 |
| `sync()` called manually | Blocking sync                                                                   |
| HTTP 304 Not Modified    | No snapshot update; `syncedAt` is not refreshed (TTL restarts from last 200 OK) |
| Quarantine active        | `ensureFresh()` skipped; no sync requests sent                                  |
| Billing overdue          | `ensureFresh()` skipped; no sync requests sent                                  |
