31 KiB
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. App‑level конфигурация (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. Хранит sharedhttpx.AsyncClient+ sharedasyncio.Semaphore+asyncio.Lockдля auth refresh. Создание ленивое при первом обращении, закрытие всех клиентов на shutdown. (RATIONALE: глобальный per‑env семафор невозможен без синглтона; REJECTED: per‑request клиенты — теряют 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). Именованные bounded‑executor'ы: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:def→async 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], но код использует syncSupersetClient— рефакторинг приводит код в соответствие с контрактом. RATIONALE: создание sync-клиента в async def запрещено FR-003; REJECTED: оставить sync — блокирует event loop) - T019 [US1] Обновить все остальные ручки, использующие
SupersetClient, вbackend/src/api/routes/: найти через grepSupersetClient(в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— проверка, что создание syncSupersetClient(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.sleep→asyncio.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_service→await
Tests for US2
- 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) - 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_concurrent—asyncio.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 defasync 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ручка не создаёт syncSupersetClient; (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)
- Phase 1: Setup
- Phase 2: Foundational (AsyncAPIClient + SupersetClient)
- Phase 3: US1 — все миксины и ручки
- Phase 4: US2 — Translate plugin
- STOP AND VALIDATE: Preview + schema check конкурентны (не deployable milestone — internal checkpoint)
Big-Bang Strategy (All Stories)
- Phase 1 → Phase 2 → Phase 3 (US1) → Phase 4 (US2) → Phase 5 (US3) → Phase 6 (US4) → Phase 7
- Все изменения в одном PR (big-bang)
- Тесты проходят до и после каждого этапа
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