Files
ss-tools/specs/032-translate-requests-httpx/tasks.md
busya bc32dd65ef 032: mark all tasks [x] — feature complete
Updated status in spec.md, plan.md, tasks.md, research.md,
data-model.md, ux_reference.md, quickstart.md.
All 72 tasks completed. 34/34 tests passing.
2026-06-04 21:09:14 +03:00

261 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Tasks: Async Backend Refactoring
**Input**: Design documents from `specs/032-translate-requests-httpx/`
**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/modules.md ✅
**Tests**: Все C3+ контракты требуют тестов на конкурентное выполнение и rejected-path регрессии. Тесты пишутся первыми (fail before implementation).
**Organization**: Группировка по user stories (P1→P4), каждая story независимо реализуема и тестируема.
---
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Новые зависимости и конфигурация — подготовка среды перед рефакторингом.
- [x] T001 Добавить `pytest-httpx` в dev-зависимости в `backend/pyproject.toml` (или `requirements-dev.txt`) для замены `requests_mock`
- [x] T002 [P] Добавить `aiofiles` в `backend/requirements.txt` для асинхронного файлового I/O больших файлов
- [x] T003 [P] Добавить `aiosmtplib` в `backend/requirements.txt` для асинхронной SMTP-отправки
- [x] T004 Добавить поля `connection_pool_size` (default: 20), `connection_pool_timeout` (default: 30), `request_timeout` (default: 30), `llm_timeout` (default: 120) в Pydantic-схему `EnvironmentConfig` в `backend/src/schemas/environment.py`. Decision gate: если `EnvironmentConfig` хранится как JSON/Pydantic blob — `Field(default=...)` достаточен (defaults применяются при parse). Если поля — SQLколонки — создать Alembic-миграцию с `server_default`. Applevel конфигурация (`db_executor_workers` и т.д.) выносится отдельно — не в `EnvironmentConfig`.
**Checkpoint**: Зависимости установлены, конфигурация расширена — можно начинать foundational work.
---
## Phase 2: Foundational — AsyncAPIClient (extend) + SupersetClient merge (US1 foundation) ⚠️ BLOCKS ALL STORIES
**Purpose**: Расширение существующего `AsyncAPIClient` в `async_network.py` (добавление семафора, connection pool config) и слияние sync/async `SupersetClient` в единый асинхронный клиент. Без этого невозможна миграция плагинов.
**⚠️ CRITICAL**: Ни одна user story не может стартовать до завершения этой фазы.
**Факт из кодовой базы**: `AsyncAPIClient` [C:3] уже существует в `backend/src/core/utils/async_network.py` как часть `AsyncNetworkModule` [C:5]. Он использует `httpx.AsyncClient` с `SupersetAuthCache`. `SupersetAuthCache` (общий для sync/async клиентов) и `SupersetClientPaginated` уже выделены. Задача — **расширить**, а не создать с нуля.
### Tests for Foundational
- [x] T005 [P] [US1] Написать контрактный тест для расширенного `AsyncAPIClient.request()` — проверка асинхронного HTTP с семафором, connection pool config — в `backend/tests/core/test_async_client.py` (RATIONALE: httpx.AsyncClient вместо requests.Session; REJECTED: requests_mock — не работает с httpx)
- [x] T006 [P] [US1] Написать тест семафора: `test_semaphore_limits_concurrent_requests` и `test_semaphore_timeout_returns_503` — в `backend/tests/core/test_async_client.py` (@PRE: семафор создан; @POST: лимит соблюдён, таймаут возвращает ошибку)
### Implementation for Foundational
- [x] T007 [US1] Расширить существующий `AsyncAPIClient` в `backend/src/core/utils/async_network.py`: добавить `asyncio.Semaphore(connection_pool_size)` с настраиваемым таймаутом, connection pool config (max_connections, keepalive), поддержку `connection_pool_size` и `connection_pool_timeout` из `EnvironmentConfig` (см. T004). (Факт: класс уже существует с auth-cache, httpx.AsyncClient, error translation; RATIONALE: расширение существующего кода вместо создания дубликата; REJECTED: создание нового файла async_client.py — дублирует существующий async_network.py)
- [x] T008 [US1] Создать объединённый асинхронный `SupersetClient` в `backend/src/core/superset_client/_base.py` — замена sync-кода на расширенный `AsyncAPIClient`, все методы `async def`. Поглотить сигнатуры из `backend/src/core/superset_client/async_superset_client.py`. (RATIONALE: big-bang merge sync+async; REJECTED: dual-stack — отвергнут на clarification)
- [x] T008a [US1] Реализовать `SupersetClientRegistry` в `backend/src/core/utils/client_registry.py` — синглтон-реестр долгоживущих клиентов по `env_id`. Хранит shared `httpx.AsyncClient` + shared `asyncio.Semaphore` + `asyncio.Lock` для auth refresh. Создание ленивое при первом обращении, закрытие всех клиентов на shutdown. (RATIONALE: глобальный perenv семафор невозможен без синглтона; REJECTED: perrequest клиенты — теряют connection pooling)
- [x] T008b [P] [US1] Создать helper `run_blocking(kind, fn, *args, timeout)` в `backend/src/core/utils/executors.py` — обёртка над `loop.run_in_executor(named_executor, fn)`. Именованные boundedexecutor'ы: `db_executor`, `file_executor`, `git_executor` (max_workers из конфига). Все блокирующие операции в productionпутях используют `run_blocking(kind=..., fn=...)` вместо нативного `asyncio.to_thread`. (RATIONALE: `asyncio.to_thread` не принимает параметр executor — нужен `loop.run_in_executor`; REJECTED: asyncio.to_thread — default executor без backpressure)
**Checkpoint**: AsyncAPIClient расширен (семафор + pool config), SupersetClient merged. Тесты семафора проходят. Можно мигрировать миксины и плагины.
---
## Phase 3: User Story 1 — Миграция 13 миксинов SupersetClient и API-ручек (Priority: P1) 🎯
**Goal**: Все вызовы Superset API из любых ручек и плагинов становятся неблокирующими. Event loop не блокируется при запросах к Superset.
**Independent Test**: Три параллельных вызова `get_dashboards()`, `get_dataset_detail()`, `export_dashboard()` через систему — общее время ≈ max(t1, t2, t3).
### Implementation for US1 — Mixins
- [x] T011 [P] [US1] Мигрировать `_dashboards.py` миксин в `backend/src/core/superset_client/_dashboards.py`: все методы → `async def`, @RELATION DEPENDS_ON -> [SupersetClient], @SIDE_EFFECT обновлён
- [x] T012 [P] [US1] Мигрировать `_datasets.py` миксин в `backend/src/core/superset_client/_datasets.py`: все методы → `async def`
- [x] T013 [P] [US1] Мигрировать `_charts.py` миксин в `backend/src/core/superset_client/_charts.py`: все методы → `async def`
- [x] T014 [P] [US1] Мигрировать `_sql_lab.py` миксин в `backend/src/core/superset_client/_sql_lab.py`: все методы → `async def`
- [x] T015 [P] [US1] Мигрировать `_database.py` миксин в `backend/src/core/superset_client/_database.py`: все методы → `async def`
- [x] T016 [P] [US1] Мигрировать `_import_export.py` миксин в `backend/src/core/superset_client/_import_export.py`: все методы → `async def`
- [x] T017 [P] [US1] Мигрировать оставшиеся 6 миксинов (`_thumbnail.py`, `_logs.py`, `_css.py`, `_annotation.py`, `_filter.py`, `_query_context.py`) в `backend/src/core/superset_client/_*.py`: `def``async def`. (Примечание: `_charts.py` уже мигрирован в T013)
### Implementation for US1 — API Routes
- [x] T018 [US1] Обновить `backend/src/api/routes/dashboards/_detail_routes.py`: заменить `SupersetClient(env)` на `client = await SupersetClientRegistry.get_client(env_id)` в `get_dashboard_thumbnail()` и `get_dashboard_detail()`. Удалить синхронные `client.network.request()` вызовы. (Факт из кодовой базы: контракты декларируют `@RELATION CALLS -> [AsyncSupersetClient]`, но код использует sync `SupersetClient` — рефакторинг приводит код в соответствие с контрактом. RATIONALE: создание sync-клиента в async def запрещено FR-003; REJECTED: оставить sync — блокирует event loop)
- [x] T019 [US1] Обновить все остальные ручки, использующие `SupersetClient`, в `backend/src/api/routes/`: найти через grep `SupersetClient(` в `async def`-ручках, заменить на асинхронный вызов. Включить: `_detail_routes.py`, routes миграций, dataset_review, clean_release
### Tests for US1
- [x] T020 [US1] Конкурентный тест: `test_concurrent_superset_calls` — три параллельных `await asyncio.gather(get_dashboards(), get_dataset_detail(), export_dashboard())` при одном event loop — в `backend/tests/core/test_superset_client_async.py` (SC-001: время ≈ max, не sum)
- [x] T021 [P] [US1] Тест rejected-path: `test_sync_superset_client_raises_in_async_context` — проверка, что создание sync `SupersetClient(env)` в async def вызывает ошибку/предупреждение — в `backend/tests/api/test_async_guard.py`
**Checkpoint**: Superset API полностью асинхронный. Все 13 миксинов и API-ручки мигрированы.
---
## Phase 4: User Story 2 — Translate Plugin Async (Priority: P2)
**Goal**: Предпросмотр перевода и проверка схемы выполняются конкурентно без взаимной блокировки. LLM-вызовы и поллинг SQL Lab не блокируют event loop.
**Independent Test**: POST `/translate/jobs/A/preview` одновременно с POST `/translate/check-target-schema`. Проверка схемы возвращает ответ до завершения предпросмотра (SC-001: < 3s).
### Implementation for US2
- [x] T022 [US2] Создать унифицированный `LLMHttpClient` в `backend/src/plugins/translate/_llm_async_http.py` модуль [C:4] с `async def call_openai_compatible()` на `httpx.AsyncClient`, rate-limit backoff через `asyncio.sleep` (RATIONALE: объединение дублирующихся _llm_http.py и preview_llm_client.py; REJECTED: AsyncOpenAI SDK не поддерживает кастомные base_url для OpenRouter/LiteLLM/Kilo)
- [x] T023 [US2] Обновить `backend/src/plugins/translate/_llm_call.py`: `call_llm_for_batch()` `async def`, вызов `LLMHttpClient.call_openai_compatible()` с `await`, `time.sleep` `asyncio.sleep` (RATIONALE: LLM-вызовы не должны блокировать другие запросы)
- [x] T024 [US2] Мигрировать `backend/src/plugins/translate/preview_executor.py`: `fetch_sample_rows()` `async def` через `await superset_client.get_chart_data()`, `call_llm()` `async def` через `await LLMHttpClient.call_openai_compatible()` (RATIONALE: preview использует Superset chart data + LLM оба должны быть асинхронными)
- [x] T025 [US2] Мигрировать `backend/src/plugins/translate/superset_executor.py`: `execute_sql()` и `poll_execution_status()` `async def`. `time.sleep(poll_interval)` `await asyncio.sleep(poll_interval)`. `client.network.request()` `await async_client.request()`. (Примечание: общий поллинг до 60×2s=120s; проверка схемы `check_target_schema` использует max_polls=15 параметры настраиваются вызывающим кодом) (RATIONALE: поллинг до 120s блокировал event loop; REJECTED: time.sleep блокирует)
- [x] T026 [US2] Мигрировать `backend/src/plugins/translate/_run_source.py`: `fetch_source_rows()` `async def` через асинхронный `SupersetClient`
- [x] T027 [US2] Обновить `backend/src/plugins/translate/preview.py`: `TranslationPreview.preview_rows()` `async def`, все внутренние вызовы с `await` (RATIONALE: preview C4 оркестратор, должен быть полностью асинхронным)
- [x] T028 [US2] Обновить `backend/src/plugins/translate/executor.py`: `TranslationExecutor` все методы `async def`, `_load_preview_edits()` и `_run_service` `await`
### Tests for US2
- [x] T029 [US2] Конкурентный тест: `test_preview_and_schema_check_concurrent` `asyncio.gather(preview, schema_check)`, проверить что schema_check завершается до preview в `backend/tests/plugins/translate/test_async_concurrency.py` (SC-001)
- [x] T030 [US2] Тест rejected-path: `test_polling_uses_asyncio_sleep_not_time_sleep` статическая проверка, что `time.sleep` не вызывается в `superset_executor.py` в `backend/tests/plugins/translate/test_async_concurrency.py`
- [x] T031 [P] [US2] Тест LLM rate-limit backoff: `test_llm_rate_limit_uses_asyncio_sleep` проверить, что 429 backoff не блокирует другие запросы в `backend/tests/plugins/translate/test_llm_async.py`
**Checkpoint**: Translate plugin полностью асинхронный. Preview + schema check конкурентны.
---
## Phase 5: User Story 3 — Все плагины Async (Priority: P3)
**Goal**: Backup, Git, LLM Analysis, Storage, Compilation Adapter все операции не блокируют event loop.
**Independent Test**: Бэкап 50 дашбордов и деплой из Git выполняются конкурентно.
### Implementation for US3
- [x] T032 [P] [US3] Мигрировать `backend/src/plugins/backup.py`: `BackupPlugin.execute()` заменить `SupersetClient(env)` на `client = await SupersetClientRegistry.get_client(env_id)`, все вызовы `client.get_dashboards()`, `client.export_dashboard()` с `await`. Файловый I/O через `run_blocking(kind='file', fn=shutil.copy, ...)` для мелких файлов, `aiofiles` для больших ZIP (>1MB). (RATIONALE: гибридный подход; REJECTED: asyncio.to_thread — default executor без backpressure)
- [x] T033 [P] [US3] Мигрировать `backend/src/plugins/git_plugin.py`: `_handle_sync()` и `_handle_deploy()``shutil` операции через `run_blocking(kind='file', fn=...)`, GitPython-клонирование через `run_blocking(kind='git', fn=...)`, SupersetClient → `SupersetClientRegistry.get_client(env_id)`. (RATIONALE: Gitклонирование до 30s — в bounded git_executor; REJECTED: оставить в event loop — блокирует)
- [x] T034 [P] [US3] Мигрировать `backend/src/plugins/llm_analysis/plugin.py`: `_fetch_dashboard_logs()` — заменить `client.network.request(method="GET", endpoint="/log/")` на `await async_client.request(...)` (RATIONALE: sync-вызов внутри async def — прямой блокировщик event loop)
- [x] T035 [P] [US3] Мигрировать `backend/src/plugins/storage/plugin.py`: `save_file()` — заменить `shutil.copyfileobj()` на `run_blocking(kind='file', fn=...)`; `delete_file()``shutil.rmtree` через `run_blocking(kind='file', fn=...)`
- [x] T036 [US3] Мигрировать `backend/src/core/utils/superset_compilation_adapter.py`: `_request_superset_preview()` и `_request_sql_lab_session()` — заменить `client.network.request()` на асинхронные вызовы
- [x] T037 [US3] Обновить `backend/src/core/utils/fileio.py`: добавить асинхронные обёртки `async def aread_dashboard_from_disk()`, `async def asave_and_unpack_dashboard()`, `async def acleanup_directory()` — гибрид `aiofiles` + `run_blocking(kind='file', fn=...)` (RATIONALE: см. R8 в research.md)
### Tests for US3
- [x] T038 [US3] Конкурентный тест плагинов: `test_backup_and_deploy_concurrent``asyncio.gather(backup.execute(), git_deploy.execute())`, проверить конкурентность — в `backend/tests/plugins/test_plugins_async.py`
- [x] T039 [P] [US3] Тест fileio: `test_async_file_operations_dont_block_event_loop` — запись/чтение больших файлов во время легковесного запроса — в `backend/tests/core/test_fileio_async.py`
**Checkpoint**: Все плагины асинхронные. Бэкап и деплой конкурентны.
---
## Phase 6: User Story 4 — Notifications + Task Manager + Services (Priority: P4)
**Goal**: Уведомления (SMTP, Telegram, Slack), Task Manager, EventBus, Git-сервисы — полностью асинхронные. ThreadPoolExecutor заменён на asyncio.create_task.
**Independent Test**: SMTP-отправка одновременно с API-запросом — запрос не задерживается.
### Implementation for US4
- [x] T040 [US4] Мигрировать `backend/src/services/notifications/providers.py`: `SMTPProvider.send()``async def` с `aiosmtplib.SMTP` вместо `smtplib.SMTP`; `TelegramProvider.send()``async def` с `httpx.AsyncClient.post` вместо `requests.post`; `SlackProvider.send()` → аналогично (RATIONALE: aiosmtplib — зрелая async-замена smtplib; REJECTED: asyncio.to_thread для SMTP — работает, но aiosmtplib даёт нативную интеграцию)
- [x] T041 [US4] Обновить `backend/src/services/notifications/service.py`: `NotificationService.dispatch_report()` — использовать `asyncio.gather` для параллельной отправки через все каналы
- [x] T042 [US4] Мигрировать `backend/src/core/task_manager/manager.py`: `TaskManager` — заменить `ThreadPoolExecutor(max_workers=5)` на хранение активных `asyncio.Task` в `dict[str, asyncio.Task]`. `run_task()``async def`, запуск через `asyncio.create_task()`. `cancel_task()``async def` через `task.cancel()` (RATIONALE: задачи разделяют event loop; REJECTED: ThreadPoolExecutor — изоляция задач, усложняет отмену)
- [x] T043 [US4] Мигрировать `backend/src/core/task_manager/event_bus.py`: `EventBus` — заменить `queue.Queue` + `call_soon_threadsafe` на `asyncio.Queue`. `publish()``async def`, `subscribe()``async def` async generator (RATIONALE: asyncio.Queue интегрируется с event loop без тредовой синхронизации; REJECTED: queue.Queue + call_soon_threadsafe — избыточно)
- [x] T044 [US4] Мигрировать `backend/src/core/task_manager/lifecycle.py`: `TaskLifecycle.execute_task()``async def`, использовать async context manager для жизненного цикла задачи (RATIONALE: async lifecycle без ThreadPoolExecutor.submit())
- [x] T045 [US4] Мигрировать `backend/src/services/git/_base.py`: `init_repo()`, `delete_repo()`, `_migrate_repo_directory()` — обернуть `shutil` и GitPython-операции в `run_blocking(kind='git'/'file', fn=...)`
### Tests for US4
- [x] T046 [US4] Конкурентный тест Task Manager: `test_concurrent_tasks_dont_block` — запустить две задачи через asyncio.create_task, проверить конкурентность — в `backend/tests/core/task_manager/test_async_tasks.py`
- [x] T047 [P] [US4] Тест уведомлений: `test_smtp_timeout_doesnt_block_api` — SMTP-таймаут не блокирует API-запрос — в `backend/tests/services/test_notifications_async.py`
- [x] T048 [P] [US4] Тест EventBus: `test_async_eventbus_publish_subscribe` — проверить asyncio.Queue доставку — в `backend/tests/core/task_manager/test_eventbus_async.py`
**Checkpoint**: Task Manager и уведомления асинхронные. ThreadPoolExecutor удалён.
---
## Phase 7: Polish — Test Migration, Cleanup, Tombstones, ADR
**Purpose**: Замена requests_mock на pytest_httpx, удаление sync-кода, Tombstone старых контрактов, создание ADR, финальная верификация.
### Test Migration
- [x] T049 [P] Заменить `requests_mock` на `httpx_mock` (pytest-httpx) во всех тестах модуля translate: `backend/src/plugins/translate/__tests__/test_executor.py`, `test_preview.py`, `test_target_schema.py`, `test_dialect_detection_orthogonal.py` — замена моков, сохранение assertions
- [x] T050 [P] Заменить `requests_mock` на `httpx_mock` в тестах плагинов: `backend/src/plugins/__tests__/test_backup.py`, `test_git_plugin.py`, `test_storage_plugin.py` (или соответствующие пути)
- [x] T051 [P] Заменить `requests_mock` на `httpx_mock` в тестах API: `backend/src/api/routes/__tests__/test_dashboard_routes.py`, `test_git_status_route.py` (или соответствующие пути)
- [x] T052 [P] Заменить `requests_mock` на `httpx_mock` в тестах сервисов: `backend/src/services/__tests__/test_notifications.py`, `test_git_service.py` (или соответствующие пути)
- [x] T053 [P] Заменить `requests_mock` на `httpx_mock` в тестах core: `backend/tests/core/test_superset_client.py` (или соответствующие пути)
- [x] T054 Обновить `backend/conftest.py`: добавить глобальную фикстуру `httpx_mock` для pytest-httpx
### Cleanup & Tombstones
- [x] T055 Пометить `APIClient` в `backend/src/core/utils/network.py` как Tombstone: @DEPRECATED 2026-06-04, @REPLACED_BY -> [AsyncAPIClient]. Удалить тело класса, оставить только заглушку-импорт для обратной совместимости (INV_6: не удалять контракт с входящими @RELATION)
- [x] T056 Пометить `AsyncSupersetClient` в `backend/src/core/superset_client/async_superset_client.py` как Tombstone: @DEPRECATED, @REPLACED_BY -> [SupersetClient]. Удалить тело класса (INV_6)
- [x] T057 Пометить `_llm_http.py` и `preview_llm_client.py` как Tombstone: @DEPRECATED, @REPLACED_BY -> [LLMHttpClient]. Удалить тела функций (INV_6)
- [x] T058 Удалить неиспользуемый sync-код: `import requests` где больше не нужен; удалить `time.sleep` где заменён на `asyncio.sleep`; удалить `ThreadPoolExecutor` импорт из TaskManager
### ADR
- [x] T059 Создать `docs/adr/ADR-0011-async-backend.md` — запись архитектурного решения о переходе на async-first backend: httpx.AsyncClient, big-bang миграция, asyncio.Semaphore, asyncio.create_task, rejected-пути (aiohttp, multiprocessing, async SQLAlchemy, dual-stack) (Constitution II: Decision Memory)
### Final Verification
- [x] T060 Запустить все backend-тесты: `cd backend && source .venv/bin/activate && python -m pytest -v` — все тесты должны проходить (SC-005)
- [x] T061 Запустить линтер: `cd backend && python -m ruff check .` — ноль ошибок
- [x] T062 Проверить конкурентность вручную по quickstart.md: preview + schema check параллельно, backup + deploy параллельно
- [x] T063 Проверить на утечки соединений: 500 последовательных запросов → `ss -tnp | grep CLOSE_WAIT` должен быть 0 (SC-006)
- [x] T064 [P] Проверить cascade-отмену: `test_client_disconnect_cancels_external_calls` — HTTP-запросы к LLM и Superset отменяются при разрыве соединения клиентом (FR-017, SC-008) — ручная проверка или pytest с `asyncio.wait_for` + `CancelledError`
- [x] T065 [P] Проверить CoT-логирование на всех асинхронных путях: `rg "logger\.(reason|reflect|explore)" backend/src/plugins/translate/ backend/src/core/task_manager/` — убедиться, что все C4/C5 контракты имеют belief-маркеры (FR-020)
- [x] T066 [P] Статическая проверка SC-008: (a) `rg "SupersetClient\(" backend/src/api/routes/ --files-with-matches | xargs rg "async def"` — ни одна `async def` ручка не создаёт sync `SupersetClient`; (b) `rg "time\.sleep" backend/src/plugins/translate/ backend/src/core/` → только в неисполняемых путях или заменён на `asyncio.sleep`; (c) `rg "^[ ]*def execute\(self," backend/src/plugins/ --files-with-matches` → все плагины реализуют `async def execute`; (d) `rg "import requests\b" backend/src/api/ backend/src/plugins/ backend/src/services/ backend/src/core/` → ноль совпадений (кроме legacy/tombstone файлов)
- [x] T066a [P] Инвентаризация DB-вызовов: найти все `session.query()`, `session.execute()`, `db.query()` в `backend/src/plugins/`, `backend/src/services/`, `backend/src/api/` — обернуть в `run_blocking(kind='db', fn=...)`. Проверить, что SQLAlchemy-сессии не пересекают границу потоков (FR-025)
- [x] T066b [P] Найти и заменить все прямые вызовы `requests.post/get/put/delete` вне `APIClient`/`network.py` во всём `backend/src/` (кроме testфайлов): `rg "requests\.(post|get|put|delete|request)\(" backend/src/ --files-with-matches | grep -v test | grep -v network.py` — все совпадения должны быть заменены на `httpx.AsyncClient` или `asyncio.to_thread`
- [x] T066c [P] Проверить интеграцию с APScheduler: все вызовы `TaskManager.run_task()` из scheduled jobs должны использовать `asyncio.run_coroutine_threadsafe()` или scheduler должен быть переведён на `AsyncIOScheduler`. Проверить `backend/src/core/scheduler.py`
- [x] T067 Семантический аудит: `axiom_semantic_validation audit_contracts` — все контракты валидны, нет сиротских @RELATION
- [x] T068 Семантический аудит belief-протокола: `axiom_semantic_validation audit_belief_protocol` — все C4/C5 контракты имеют @RATIONALE/@REJECTED
- [x] T069 Перестроить семантический индекс: `axiom_semantic_index rebuild rebuild_mode="full"` — индекс актуален
---
## Dependencies & Execution Order
### Phase Dependencies
| Phase | Depends On | Blocks |
|-------|-----------|--------|
| Phase 1: Setup | — | — |
| Phase 2: Foundational (US1 foundation) | Phase 1 | **ВСЕ user stories** |
| Phase 3: US1 — SupersetClient | Phase 2 | — |
| Phase 4: US2 — Translate | Phase 2 + Phase 3 | — |
| Phase 5: US3 — Plugins | Phase 2 + Phase 3 | — |
| Phase 6: US4 — Notifications + TM | Phase 2 | — |
| Phase 7: Polish | Phase 3 + 4 + 5 + 6 | — |
### User Story Independence
- **US1 (P1)**: Зависит от Phase 2. Не зависит от US2/US3/US4. Может быть задеплоена независимо — все плагины уже могут использовать асинхронный SupersetClient.
- **US2 (P2)**: Зависит от US1 (SupersetClient должен быть готов). Не зависит от US3/US4.
- **US3 (P3)**: Зависит от US1. Не зависит от US2/US4. Плагины мигрируются независимо друг от друга (T032-T035 — параллельны).
- **US4 (P4)**: Зависит от Phase 2 (AsyncAPIClient). Не зависит от US1/US2/US3. Task Manager и уведомления независимы от плагинов.
### Within Each Phase
- **Все Txxx [P]** — могут выполняться параллельно (разные файлы)
- **Тесты пишутся ПЕРВЫМИ** в каждой фазе — должны FAIL до реализации
- **Implementation after tests** — последовательно или параллельно (по маркировке [P])
### Parallel Opportunities
- Phase 2: T005, T006 (тесты) — параллельно (разные test functions в одном файле)
- Phase 3: T011-T017 (13 миксинов) — все параллельно
- Phase 5: T032-T035 (4 плагина) — все параллельно
- Phase 6: T040 (notifications), T042 (TaskManager), T045 (git services) — параллельно
- Phase 7: T049-T053 (test migration) — все параллельно
---
## Implementation Strategy
### Internal Validation Checkpoint (US1 + US2)
1. Phase 1: Setup
2. Phase 2: Foundational (AsyncAPIClient + SupersetClient)
3. Phase 3: US1 — все миксины и ручки
4. Phase 4: US2 — Translate plugin
5. **STOP AND VALIDATE**: Preview + schema check конкурентны (не deployable milestone — internal checkpoint)
### Big-Bang Strategy (All Stories)
1. Phase 1 → Phase 2 → Phase 3 (US1) → Phase 4 (US2) → Phase 5 (US3) → Phase 6 (US4) → Phase 7
2. Все изменения в одном PR (big-bang)
3. Тесты проходят до и после каждого этапа
---
## Notes
- [P] tasks = разные файлы, без зависимостей — можно параллельно
- [USx] label = привязка к user story для трассируемости
- Каждая story проверяется независимо (T020, T029, T038, T046)
- Ручная проверка конкурентности обязательна (T062) — автоматические тесты могут не поймать все race conditions
- Big-bang: sync-код НЕ сохраняется для обратной совместимости
- Tombstone по INV_6: не удалять контракты с входящими @RELATION — помечать @DEPRECATED
- REJECTED пути защищены тестами: T021 (sync client в async def), T030 (time.sleep в поллинге)
- Для C4 контрактов: @RATIONALE и @REJECTED обязательны — проверяется T068