# [DEF:ADR-0011:ADR]
# @STATUS ACTIVE
# @PURPOSE Документировать архитектурное решение о полном переходе backend на async-first архитектуру: замена синхронного `requests.Session` на `httpx.AsyncClient`, введение глобальных per‑env семафоров, bounded executors и полный переход Task Manager на asyncio.
# @RATIONALE ss-tools одновременно взаимодействует с несколькими внешними системами (Superset REST API, LLM API, SMTP, Telegram, Slack) и выполняет блокирующие операции (Git, файловый I/O, SQLAlchemy). При одном воркере uvicorn синхронные блокирующие вызовы останавливают все входящие HTTP-запросы. Единственный способ обеспечить конкурентность — сделать весь I/O неблокирующим.
# @REJECTED aiohttp — отвергнут в пользу httpx, который уже был в зависимостях (0.28.1) и имеет лучший API для async HTTP.
# @REJECTED Полный переход на async SQLAlchemy — потребовал бы переписывания всех моделей и репозиториев, что выходит за рамки этой фичи. `run_blocking` с bounded executor достаточен.
# @REJECTED Dual-stack (sync + async) — потребовал бы поддержки двух транспортов, увеличивая surface для ошибок и усложняя код.
# @REJECTED Wrapper-only (`asyncio.to_thread` на всё) — rejected, т.к. `asyncio.to_thread` не принимает executor и использует default пул без backpressure.
# @REJECTED Multiprocessing / `--workers N` — rejected, т.к. увеличение воркеров не решает проблему блокировки внутри одного воркера и увеличивает потребление памяти.

## Decision

Весь backend переводится на async-first архитектуру:

### Transport Layer

1. **HTTP-клиент**: `requests.Session` → `httpx.AsyncClient` через класс `AsyncAPIClient` в `core/utils/async_network.py`.
2. **SupersetClient**: sync + AsyncSupersetClient объединены в единый асинхронный клиент. Все 13 миксинов — `async def`.
3. **Семафоры**: Глобальный per‑env `asyncio.Semaphore` через синглтон `SupersetClientRegistry` (`core/utils/client_registry.py`). При исчерпании — таймаут 30s → 503.
4. **LLM-клиент**: Унифицированный `LLMHttpClient` в `plugins/translate/_llm_async_http.py` на `httpx.AsyncClient`. Rate-limit backoff через `asyncio.sleep`.

### Blocking Operations

5. **Bounded executors**: Именованные `ThreadPoolExecutor` (db, file, git) через helper `run_blocking(kind, fn)` в `core/utils/executors.py`. Вместо `asyncio.to_thread`.
6. **Graceful shutdown**: Все клиенты закрываются при shutdown через `SupersetClientRegistry.shutdown()`. Незавершённые задачи помечаются CANCELLED.

### Task Manager

7. **Запуск задач**: `ThreadPoolExecutor` → `asyncio.create_task`. Задачи разделяют основной event loop.
8. **EventBus**: `queue.Queue` + `call_soon_threadsafe` → `asyncio.Queue(maxsize=10000)` с fan‑out на подписчиков.
9. **Lifecycle**: Thread-based → async context manager.

### Конфигурация

10. **Per‑env**: `connection_pool_size`, `connection_pool_timeout`, `request_timeout`, `llm_timeout` в `EnvironmentConfig`.
11. **App‑level**: `db_executor_workers`, `file_executor_workers`, `git_executor_workers`, `graceful_shutdown_timeout` в `AppAsyncRuntimeConfig`.

### Тестирование

12. `requests_mock` → `pytest-httpx` для мокирования асинхронных HTTP-вызовов.
13. Тесты конкурентности с `asyncio.gather` для всех C3+ контрактов.
14. Rejected-path regression тесты для каждого запрещённого паттерна.

### Модули, затронутые рефакторингом

- `core/utils/`: `async_network.py` (+semaphore), `client_registry.py` (new), `executors.py` (new), `fileio.py` (+async wrappers), `network.py` → Tombstone, `superset_compilation_adapter.py` → async
- `core/superset_client/`: `_base.py` (+13 mixins) → все async
- `core/task_manager/`: `manager.py`, `event_bus.py`, `lifecycle.py` → все async
- `api/routes/`: dashboards, assistant, migration, datasets, git, environments, profile → все async
- `plugins/translate/`: `_llm_async_http.py` (new), все файлы → async
- `plugins/`: `backup.py`, `git_plugin.py`, `llm_analysis/plugin.py`, `storage/plugin.py` → async
- `services/`: `notifications/providers.py`, `notifications/service.py`, `git/_base.py` → async
- `services/`: `git/_base.py`, `git/_branch.py`, `git/_sync.py`, `git/_status.py`, `git/_merge.py` → async

# [/DEF:ADR-0011:ADR]
