# Ошибки и rate limits

## HTTP status coды

Ordinis следует стандартам REST с расширениями:

| Status | Используется для | Можно retry? |
|---|---|---|
| `200 OK` | GET / PUT успешен | — |
| `201 Created` | POST создан новый ресурс | — |
| `202 Accepted` | Async операция принята | — |
| `204 No Content` | DELETE / close успешен | — |
| `400 Bad Request` | Malformed JSON / missing required fields | Нет — fix payload |
| `401 Unauthorized` | Token expired / invalid / missing | Refresh token и retry |
| `403 Forbidden` | Token валиден, но scope не позволяет | Нет — request higher role |
| `404 Not Found` | Resource не существует / закрыт / out of scope | Зависит от context |
| `409 Conflict` | Optimistic lock fail / cascade block | Refetch + retry |
| `422 Unprocessable Entity` | Validation errors (schema / x-references) | Нет — fix data |
| `429 Too Many Requests` | Rate limit hit | Wait + retry с backoff |
| `500 Internal Server Error` | Server bug | Retry с exponential backoff |
| `502 Bad Gateway` | Upstream (config-server, Vault) недоступен | Retry |
| `503 Service Unavailable` | Postgres недоступен / restart | Retry с long backoff |
| `504 Gateway Timeout` | Запрос > timeout (typically 30s) | Retry |

## Error response format

Все 4xx/5xx с body имеют единый shape:

```json
{
  "timestamp": "2026-05-07T15:23:45.123+03:00",
  "status": 422,
  "error": "Validation Error",
  "errors": [
    {
      "path": "/data/satelliteTypeCode",
      "code": "x_references_target_not_found",
      "message": "Referenced record not found in 'satellite_type' for code='UNKNOWN'"
    },
    {
      "path": "/data/launchDate",
      "code": "schema_format_invalid",
      "message": "Expected ISO 8601 date, got 'tomorrow'"
    }
  ],
  "traceId": "abc123-def456-789"
}
```

| Поле | Описание |
|---|---|
| `timestamp` | Server time когда ошибка возникла. |
| `status` | HTTP status code. |
| `error` | Категория (`Validation Error`, `Authorization Error` и т.д.). |
| `errors` | Array of detail errors. Может быть один или несколько. |
| `errors[].path` | JSON Pointer к полю с ошибкой (например `/data/launchDate`). |
| `errors[].code` | Machine-readable код для programmatic handling. |
| `errors[].message` | Human-readable описание (русский для admin UI, английский для consumers). |
| `traceId` | Tempo trace ID — приложи при эскалации в Ordinis team. |

## Известные error codes

### Validation (422)

| Code | Описание | Fix |
|---|---|---|
| `schema_required_missing` | Required field пропущен | Добавить поле |
| `schema_format_invalid` | Wrong format (date / email / etc) | Fix format |
| `schema_type_mismatch` | Wrong JSON type (string vs number) | Fix type |
| `x_references_dict_not_found` | Referenced dictionary не существует | Schema bug — escalate |
| `x_references_target_not_found` | Referenced record не активен / out of scope | Use existing key либо upgrade scope |
| `x_references_non_string` | v1 поддерживает только string refs | Fix schema |
| `x_references_malformed` | Bad x-references syntax | Fix schema |
| `x_localized_default_missing` | i18n field без `default_locale` | Add default locale |
| `geometry_invalid_wkt` | Невалидный WKT для geometry | Fix WKT |
| `geometry_invalid_lat_lon` | lat/lon вне диапазона | Use valid coords |

### Authorization (401/403)

| Code | Описание |
|---|---|
| `auth_token_missing` | No `Authorization` header |
| `auth_token_invalid` | Token не verified (signature, issuer) |
| `auth_token_expired` | Token expired |
| `auth_scope_insufficient` | Scope не покрывает запрашиваемый словарь |

### Conflict (409)

| Code | Описание | Fix |
|---|---|---|
| `optimistic_lock_failed` | Запись изменилась между read и write | Refetch + retry |
| `record_already_closed` | close() на уже закрытой записи | No-op либо ignore |
| `bulk_partial_failure` | Bulk операция: часть успешна, часть нет | Check `errors` array в response |

### Server (5xx)

| Code | Описание |
|---|---|
| `internal_unexpected` | Server bug — escalate с traceId |
| `db_unavailable` | БД недоступна — retry с long backoff |
| `dependent_unavailable` | Зависимый сервис (config, secrets) недоступен — retry |
| `event_publish_failed` | Запись в БД ОК, но события не отправлены — eventually consistent (events доедут позже) |

## Rate limits

Сегодня (v1.2.1) Ordinis **не имеет explicit rate limits**. Но есть soft
limits через resource constraints:

| Resource | Limit |
|---|---|
| Postgres connections | 200 (shared между read-api + writer + projection-writer) |
| Read-api app pods | 2× в проде, 1× CPU + 512MB memory |
| Writer pods | 2× в проде |
| Concurrent JWT verifications | bounded heap (~5k/sec практический максимум) |

При превышении (типично DDoS-like patterns):
- HTTP `429 Too Many Requests` после ingress rate limit.
- HTTP `503 Service Unavailable` при exhausting connection pool.

### Recommended client-side limits

Чтобы не triggers backend limits:

| Сценарий | Recommended rate |
|---|---|
| Cold load на startup | `≤ 100 req/sec` parallel, then cache |
| Steady state read | `≤ 50 req/sec` per consumer |
| Bulk import / migration | Talk to Ordinis team первым |

Внутренний `RateLimiter` (Caffeine `Bucket4j` — TBD) — в roadmap для v2 если
наблюдаются abuse patterns.

## Retry strategy

```python
import time, random
from typing import Callable

def retry_with_backoff(
    fn: Callable, max_attempts: int = 5, base: float = 0.5
) -> any:
    for attempt in range(max_attempts):
        try:
            return fn()
        except (HTTPError, TimeoutError) as e:
            if attempt == max_attempts - 1:
                raise
            if e.status_code in (400, 401, 403, 404, 422):
                raise  # don't retry permanent errors
            sleep = base * (2 ** attempt) + random.uniform(0, 0.5)
            time.sleep(sleep)
```

## Tracing

Каждый response имеет `X-Trace-Id` header и `traceId` field в error body.

При эскалации Ordinis team:
1. Скопируй `traceId` из response либо логов.
2. Создай issue с воспроизведением.
3. Ordinis team поднимет trace по этому ID — full path запроса виден.

Без `traceId` investigation занимает существенно больше времени —
обязательно сохраняй его при ошибках.

## Дальше

- [Best practices](best-practices.md) — как писать robust integration code
- [Авторизация](auth.md) — как избежать 401/403
