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

31 KiB
Raw Permalink Blame History

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: Новые зависимости и конфигурация — подготовка среды перед рефакторингом.

  • T001 Добавить pytest-httpx в dev-зависимости в backend/pyproject.toml (или requirements-dev.txt) для замены requests_mock
  • T002 [P] Добавить aiofiles в backend/requirements.txt для асинхронного файлового I/O больших файлов
  • T003 [P] Добавить aiosmtplib в backend/requirements.txt для асинхронной SMTP-отправки
  • 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

  • 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)
  • 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

  • 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)
  • 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)
  • 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)
  • 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

  • T011 [P] [US1] Мигрировать _dashboards.py миксин в backend/src/core/superset_client/_dashboards.py: все методы → async def, @RELATION DEPENDS_ON -> [SupersetClient], @SIDE_EFFECT обновлён
  • T012 [P] [US1] Мигрировать _datasets.py миксин в backend/src/core/superset_client/_datasets.py: все методы → async def
  • T013 [P] [US1] Мигрировать _charts.py миксин в backend/src/core/superset_client/_charts.py: все методы → async def
  • T014 [P] [US1] Мигрировать _sql_lab.py миксин в backend/src/core/superset_client/_sql_lab.py: все методы → async def
  • T015 [P] [US1] Мигрировать _database.py миксин в backend/src/core/superset_client/_database.py: все методы → async def
  • T016 [P] [US1] Мигрировать _import_export.py миксин в backend/src/core/superset_client/_import_export.py: все методы → async def
  • T017 [P] [US1] Мигрировать оставшиеся 6 миксинов (_thumbnail.py, _logs.py, _css.py, _annotation.py, _filter.py, _query_context.py) в backend/src/core/superset_client/_*.py: defasync def. (Примечание: _charts.py уже мигрирован в T013)

Implementation for US1 — API Routes

  • 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)
  • T019 [US1] Обновить все остальные ручки, использующие SupersetClient, в backend/src/api/routes/: найти через grep SupersetClient( в async def-ручках, заменить на асинхронный вызов. Включить: _detail_routes.py, routes миграций, dataset_review, clean_release

Tests for US1

  • 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)
  • 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

  • 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)
  • T023 [US2] Обновить backend/src/plugins/translate/_llm_call.py: call_llm_for_batch()async def, вызов LLMHttpClient.call_openai_compatible() с await, time.sleepasyncio.sleep (RATIONALE: LLM-вызовы не должны блокировать другие запросы)
  • 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 — оба должны быть асинхронными)
  • 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 — блокирует)
  • T026 [US2] Мигрировать backend/src/plugins/translate/_run_source.py: fetch_source_rows()async def через асинхронный SupersetClient
  • T027 [US2] Обновить backend/src/plugins/translate/preview.py: TranslationPreview.preview_rows()async def, все внутренние вызовы с await (RATIONALE: preview — C4 оркестратор, должен быть полностью асинхронным)
  • T028 [US2] Обновить backend/src/plugins/translate/executor.py: TranslationExecutor → все методы async def, _load_preview_edits() и _run_serviceawait

Tests for US2

  • T029 [US2] Конкурентный тест: test_preview_and_schema_check_concurrentasyncio.gather(preview, schema_check), проверить что schema_check завершается до preview — в backend/tests/plugins/translate/test_async_concurrency.py (SC-001)
  • 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
  • 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

  • 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)
  • 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 — блокирует)
  • 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)
  • 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=...)
  • T036 [US3] Мигрировать backend/src/core/utils/superset_compilation_adapter.py: _request_superset_preview() и _request_sql_lab_session() — заменить client.network.request() на асинхронные вызовы
  • 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

  • T038 [US3] Конкурентный тест плагинов: test_backup_and_deploy_concurrentasyncio.gather(backup.execute(), git_deploy.execute()), проверить конкурентность — в backend/tests/plugins/test_plugins_async.py
  • 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

  • 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 даёт нативную интеграцию)
  • T041 [US4] Обновить backend/src/services/notifications/service.py: NotificationService.dispatch_report() — использовать asyncio.gather для параллельной отправки через все каналы
  • 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 — изоляция задач, усложняет отмену)
  • 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 — избыточно)
  • T044 [US4] Мигрировать backend/src/core/task_manager/lifecycle.py: TaskLifecycle.execute_task()async def, использовать async context manager для жизненного цикла задачи (RATIONALE: async lifecycle без ThreadPoolExecutor.submit())
  • 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

  • T046 [US4] Конкурентный тест Task Manager: test_concurrent_tasks_dont_block — запустить две задачи через asyncio.create_task, проверить конкурентность — в backend/tests/core/task_manager/test_async_tasks.py
  • T047 [P] [US4] Тест уведомлений: test_smtp_timeout_doesnt_block_api — SMTP-таймаут не блокирует API-запрос — в backend/tests/services/test_notifications_async.py
  • 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

  • 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
  • T050 [P] Заменить requests_mock на httpx_mock в тестах плагинов: backend/src/plugins/__tests__/test_backup.py, test_git_plugin.py, test_storage_plugin.py (или соответствующие пути)
  • T051 [P] Заменить requests_mock на httpx_mock в тестах API: backend/src/api/routes/__tests__/test_dashboard_routes.py, test_git_status_route.py (или соответствующие пути)
  • T052 [P] Заменить requests_mock на httpx_mock в тестах сервисов: backend/src/services/__tests__/test_notifications.py, test_git_service.py (или соответствующие пути)
  • T053 [P] Заменить requests_mock на httpx_mock в тестах core: backend/tests/core/test_superset_client.py (или соответствующие пути)
  • T054 Обновить backend/conftest.py: добавить глобальную фикстуру httpx_mock для pytest-httpx

Cleanup & Tombstones

  • T055 Пометить APIClient в backend/src/core/utils/network.py как Tombstone: @DEPRECATED 2026-06-04, @REPLACED_BY -> [AsyncAPIClient]. Удалить тело класса, оставить только заглушку-импорт для обратной совместимости (INV_6: не удалять контракт с входящими @RELATION)
  • T056 Пометить AsyncSupersetClient в backend/src/core/superset_client/async_superset_client.py как Tombstone: @DEPRECATED, @REPLACED_BY -> [SupersetClient]. Удалить тело класса (INV_6)
  • T057 Пометить _llm_http.py и preview_llm_client.py как Tombstone: @DEPRECATED, @REPLACED_BY -> [LLMHttpClient]. Удалить тела функций (INV_6)
  • T058 Удалить неиспользуемый sync-код: import requests где больше не нужен; удалить time.sleep где заменён на asyncio.sleep; удалить ThreadPoolExecutor импорт из TaskManager

ADR

  • 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

  • T060 Запустить все backend-тесты: cd backend && source .venv/bin/activate && python -m pytest -v — все тесты должны проходить (SC-005)
  • T061 Запустить линтер: cd backend && python -m ruff check . — ноль ошибок
  • T062 Проверить конкурентность вручную по quickstart.md: preview + schema check параллельно, backup + deploy параллельно
  • T063 Проверить на утечки соединений: 500 последовательных запросов → ss -tnp | grep CLOSE_WAIT должен быть 0 (SC-006)
  • T064 [P] Проверить cascade-отмену: test_client_disconnect_cancels_external_calls — HTTP-запросы к LLM и Superset отменяются при разрыве соединения клиентом (FR-017, SC-008) — ручная проверка или pytest с asyncio.wait_for + CancelledError
  • T065 [P] Проверить CoT-логирование на всех асинхронных путях: rg "logger\.(reason|reflect|explore)" backend/src/plugins/translate/ backend/src/core/task_manager/ — убедиться, что все C4/C5 контракты имеют belief-маркеры (FR-020)
  • 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 файлов)
  • 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)
  • 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
  • T066c [P] Проверить интеграцию с APScheduler: все вызовы TaskManager.run_task() из scheduled jobs должны использовать asyncio.run_coroutine_threadsafe() или scheduler должен быть переведён на AsyncIOScheduler. Проверить backend/src/core/scheduler.py
  • T067 Семантический аудит: axiom_semantic_validation audit_contracts — все контракты валидны, нет сиротских @RELATION
  • T068 Семантический аудит belief-протокола: axiom_semantic_validation audit_belief_protocol — все C4/C5 контракты имеют @RATIONALE/@REJECTED
  • 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