# Build 060.24 — Runtime Recovery Architecture **Engineering Migration Report** --- ## Контроль документа | Свойство | Значение | |---|---| | Build | 060.24 | | Название | Runtime Recovery Architecture | | Статус | Completed | | Проект | Dzentra | | Подсистема | Market Data Acquisition | | Компонент | Trade Stream Runtime | | Версия документа | 2.0 | ## Связанные документы - [Build 060.24 Architecture](build_060_24_architecture.md) — итоговая архитектурная спецификация Build. - [Build 060.23 Engineering Migration Report](build_060_23.md) — Trade Stream Acquisition Integration. - [Build 060.23 Architecture](build_060_23_architecture.md) — архитектура интеграции Trade Stream Acquisition. - [Build 060.22 Engineering Migration Report](build_060_22.md) — Runtime Service Integration. - [Build 060.20.1 Engineering Migration Report](build_060_20_1.md) — Trade Stream State Ownership Alignment. - [Build 060.19 Engineering Migration Report](build_060_19.md) — Trade Recovery. - [Build 060.18 Engineering Migration Report](build_060_18.md) — Trade Stream Consistency. --- ## 1. Исходное состояние После Build 060.23 проект имел рабочий конвейер обработки одной live-сделки: ```text WebSocket message │ ▼ TradeStreamMessageAdapterProtocol │ ▼ Canonical Trade │ ▼ TradeStreamConsistencyController │ ▼ Trade | None ``` Также уже существовал REST Recovery Layer: ```text TradeRecoveryRequest │ ▼ TradeRecoveryController │ ▼ TradeRecoveryNormalizer │ ▼ TradeStreamConsistencyController ``` Эти два конвейера использовали Consistency Layer, но Runtime ещё не обладал архитектурой обнаружения потери активности, повторного подключения и координации восстановления пропущенного диапазона. ## 2. Цель Build Цель Build 060.24 — реализовать внутреннюю Runtime Recovery Architecture: - определить единственный checkpoint Trade Stream; - представить и рассчитать допустимые окна восстановления; - реализовать изолированные runtime-компоненты reconnect, heartbeat, supervisor и scheduler; - связать checkpoint, planner и существующий Recovery Controller; - построить единый граф зависимостей Live Stream и Recovery; - не запускать production lifecycle и не создавать сетевые подключения во время композиции. Build создаёт завершённую архитектурную основу восстановления в памяти. Её подключение к реальному WebSocket lifecycle, bootstrap приложения и production-конфигурации относится к Build 060.25. --- ## 3. Выполненный roadmap | Подэтап | Название | Фактическая реализация | |---|---|---| | 060.24.1 | RuntimeCheckpoint | `TradeStreamState.last_trade` | | 060.24.2 | RuntimeSessionState | `RuntimeSupervisorState` и локальные состояния runtime-компонентов | | 060.24.3 | RecoveryPlanner | `TradeRecoveryWindowPlanner` | | 060.24.4 | RecoveryWindowCalculator | алгоритм `TradeRecoveryWindowPlanner.build_windows()` | | 060.24.5 | ReconnectCoordinator | `ReconnectCoordinator` | | 060.24.6 | Heartbeat | `HeartbeatMonitor` | | 060.24.7 | Supervisor | `RuntimeSupervisor` | | 060.24.8 | Scheduler | `RuntimeScheduler` | | 060.24.9.1 | RuntimeRecoveryCoordinator | `RuntimeRecoveryProtocol` и `RuntimeRecoveryCoordinator` | | 060.24.9.2 | Runtime Composition | `TradeStreamRuntimeComposition` и factory-функция сборки | `RuntimeSessionState` не введён как отдельная дублирующая модель. Состояние runtime-сессии выражено через `RuntimeSupervisorState`, а локальные детали принадлежат `ReconnectState` и `HeartbeatState`. `RecoveryWindowCalculator` не введён как отдельный класс. Расчёт является единственной ответственностью `TradeRecoveryWindowPlanner`, поэтому его выделение создало бы искусственный слой без самостоятельного состояния или политики. --- ## 4. Реализованные компоненты ### 4.1. RuntimeCheckpoint `TradeStreamState` хранит: ```text last_trade_id last_trade recent trade window ``` Checkpoint — это `last_trade`, то есть последняя каноническая сделка, принятая Consistency Layer. Он обновляется только после успешного `accept()`. Полный дубликат, конфликтующий дубликат, нарушение порядка или неожиданный symbol не переводят checkpoint вперёд. Основной инвариант: ```text last_trade is None ⇔ last_trade_id is None ``` Если checkpoint существует: ```text last_trade.trade_id == last_trade_id last_trade.symbol == TradeStreamState.symbol ``` Checkpoint находится только в оперативной памяти. Его долговременное хранение относится к Build 060.28. ### 4.2. TradeRecoveryWindow `TradeRecoveryWindow` — immutable value object с `slots`: ```text symbol start_time end_time ``` Модель проверяет типы и запрещает: - пустой symbol; - отрицательные временные границы; - `start_time > end_time`; - использование `bool` вместо целочисленного timestamp. Window не выполняет Recovery и не содержит transport-, retry- или exchange-состояние. ### 4.3. TradeRecoveryWindowPlanner Planner принимает полный диапазон и возвращает: ```text tuple[TradeRecoveryWindow, ...] ``` Каждое окно удовлетворяет ограничению существующего `TradeRecoveryRequest`: ```text end_time - start_time < 3_600_000 ms ``` Значение по умолчанию: ```text 3_599_999 ms ``` Соседние окна имеют общую границу: ```text previous.end_time == next.start_time ``` Возможный повтор сделки на границе устраняется общим Consistency Layer. При равных `start_time` и `end_time` Planner возвращает пустой tuple. Planner: - не выполняет REST-запросы; - не создаёт `TradeRecoveryRequest`; - не читает системное время; - не зависит от WebSocket и Runtime lifecycle. ### 4.4. ReconnectCoordinator `ReconnectCoordinator` выполняет одну попытку reconnect: ```text ReconnectStartedEvent │ ▼ ConnectCommand │ ▼ restore_subscriptions() │ ▼ ReconnectCompletedEvent ``` При ошибке публикуется `ReconnectFailedEvent`, состояние переходит в `FAILED`, а исходное исключение распространяется вызывающему коду. Coordinator хранит номер попытки и состояние: ```text DISCONNECTED CONNECTING RESTORING_SUBSCRIPTIONS CONNECTED FAILED ``` Retry loop и backoff в этот компонент не входят. ### 4.5. HeartbeatMonitor Heartbeat является пассивным монитором: - использует monotonic clock; - хранит время последней активности; - определяет timeout детерминированным вызовом `check_timeout()`; - публикует один `HeartbeatTimeoutEvent` на один период отсутствия активности; - возвращается в monitoring после новой активности. Heartbeat не создаёт фоновых задач, не выполняет reconnect и не управляет WebSocket. ### 4.6. RuntimeSupervisor Supervisor владеет состоянием runtime-сессии: ```text STOPPED RUNNING RECONNECTING FAILED ``` Он: - запускает и останавливает Heartbeat; - принимает уведомления об активности; - запускает одну reconnect-попытку после подтверждённого timeout; - предотвращает параллельный reconnect; - после успешного reconnect начинает новый heartbeat-период; - сохраняет `FAILED` и распространяет ошибку reconnect. Supervisor не выполняет периодический цикл, recovery, retry или backoff. ### 4.7. RuntimeScheduler Scheduler отвечает только за периодичность: ```text HeartbeatMonitor.check_timeout() │ ├── False → следующий интервал │ └── True → RuntimeSupervisor.handle_heartbeat_timeout() ``` Цикл запускается явным `await RuntimeScheduler.start()`. Scheduler не создаёт `asyncio.Task` самостоятельно. `stop()` запрашивает безопасное завершение на ближайшей управляемой границе. ### 4.8. RuntimeRecoveryCoordinator Coordinator соединяет существующие слои: ```text TradeStreamStateStore │ ▼ TradeStreamState.last_trade │ ▼ executed_at → Unix milliseconds │ ▼ TradeRecoveryWindowPlanner │ ▼ TradeRecoveryRequest[] │ ▼ TradeRecoveryController │ ▼ aggregated TradeRecoveryResult ``` Свойства реализации: - использует существующее состояние и не создаёт новый `TradeStreamState`; - принимает `recovery_end_time` от вызывающего runtime-компонента; - преобразует только timezone-aware `datetime`; - не использует float при переводе времени в Unix milliseconds; - выполняет окна последовательно; - объединяет восстановленные сделки в один результат; - не скрывает ошибки Store, Planner или Recovery Controller. При отсутствии состояния или checkpoint возвращается пустой `TradeRecoveryResult`; обе его границы равны `recovery_end_time`. ### 4.9. Runtime Composition Factory: ```text build_trade_stream_runtime_composition(...) ``` создаёт один граф зависимостей и возвращает immutable `TradeStreamRuntimeComposition`. Ключевой identity-инвариант: ```text Live Stream ───┐ ├── one TradeStreamConsistencyController Recovery ──────┘ │ ▼ one TradeStreamStateStore ``` Благодаря этому live-сделки и восстановленные сделки проходят одинаковые правила согласованности и используют один checkpoint. Внешние WebSocket-зависимости передаются через Protocol-контракты. Создание Composition: - не читает Settings; - не создаёт production transport; - не подключается к сети; - не запускает Scheduler или Supervisor; - не создаёт фоновые задачи; - не выполняет Recovery. --- ## 5. Архитектурные границы После Build действуют следующие границы. | Компонент | Владеет | Не владеет | |---|---|---| | `TradeStreamState` | checkpoint и deduplication window | recovery orchestration | | `TradeRecoveryWindowPlanner` | расчёт окон | REST и runtime lifecycle | | `ReconnectCoordinator` | одна reconnect-попытка | retry, backoff и recovery | | `HeartbeatMonitor` | контроль активности | scheduling и reconnect | | `RuntimeSupervisor` | состояние сессии и реакция на timeout | периодический цикл и recovery | | `RuntimeScheduler` | периодический вызов проверок | reconnect-логика и heartbeat-вычисления | | `RuntimeRecoveryCoordinator` | recovery orchestration одного symbol | WebSocket lifecycle | | Composition root | создание и связывание объектов | запуск production lifecycle | Ни один из компонентов Build не импортирует bootstrap приложения, торговую логику или Telegram-инфраструктуру. --- ## 6. Состав изменений ### Production ```text app/src/market_data/acquisition/ ├── consistency/ │ └── trade_stream_state.py ├── recovery/ │ ├── trade_recovery_window.py │ └── trade_recovery_window_planner.py ├── runtime/ │ ├── heartbeat.py │ ├── reconnect.py │ ├── runtime_recovery_coordinator.py │ ├── runtime_recovery_protocol.py │ ├── scheduler.py │ └── supervisor.py └── trade_stream_runtime_composition.py ``` ### Tests ```text app/tests/unit/market_data/acquisition/ ├── consistency/ │ └── test_trade_stream_state.py ├── recovery/ │ ├── test_trade_recovery_window.py │ └── test_trade_recovery_window_planner.py ├── runtime/ │ ├── test_heartbeat_monitor.py │ ├── test_reconnect_coordinator.py │ ├── test_runtime_recovery_coordinator.py │ ├── test_runtime_scheduler.py │ └── test_runtime_supervisor.py └── test_trade_stream_runtime_composition.py ``` ### Documentation ```text docs/migrations/ ├── build_060_24.md └── build_060_24_architecture.md ``` Изменение `.gitignore`, присутствующее в рабочем дереве, не является частью Build 060.24. --- ## 7. Тестирование Непосредственно связанные с Build девять тестовых модулей содержат 230 тестов. | Область | Тестов | |---|---:| | Trade Stream State | 24 | | Recovery Window | 20 | | Recovery Window Planner | 36 | | Reconnect Coordinator | 15 | | Heartbeat Monitor | 28 | | Runtime Supervisor | 25 | | Runtime Scheduler | 30 | | Runtime Recovery Coordinator | 27 | | Runtime Composition | 25 | | **Итого** | **230** | Контрольный результат: ```text 230 passed ``` Полная регрессия проекта: ```text 1620 passed 0 failed ``` Тесты являются unit- и composition-тестами. Реальные сценарии WebSocket disconnect, повторной подписки, reconnect → recovery и стресс-тестирование относятся к Build 060.25–060.26. --- ## 8. Обратная совместимость Build сохраняет существующие публичные контракты: - каноническая модель `Trade` не изменена; - Recovery Controller и Recovery Request не изменены; - live acquisition продолжает использовать Consistency Protocol; - transport protocols остаются внешними зависимостями Runtime; - `main.py` и bootstrap не изменены. Расширение `TradeStreamState` добавляет checkpoint, не изменяя контракт `accept()`. --- ## 9. Что не входит в Build Build 060.24 не включает: - создание production WebSocket transport/session; - подключение Composition к `main.py` или bootstrap; - запуск scheduler-задачи; - передачу live-активности в Supervisor; - выбор symbol и `recovery_end_time` для production recovery; - автоматическую цепочку reconnect → recovery; - переключение между Recovery и Live Stream; - retry/backoff policy; - постоянное хранение checkpoint; - интеграционные reconnect/recovery и stress tests. Эти ограничения означают, что формулировка «Trade Stream самостоятельно переживает обрыв WebSocket» на данном этапе является архитектурной целью, а не активированным production-поведением. Production Runtime Integration выполняется в Build 060.25. --- ## 10. Definition of Done - [x] Checkpoint принадлежит `TradeStreamState`. - [x] Checkpoint обновляется только принятой сделкой. - [x] Реализована immutable-модель Recovery Window. - [x] Реализован расчёт одного или нескольких допустимых окон. - [x] Реализована одна reconnect-попытка и восстановление подписок. - [x] Реализован пассивный Heartbeat. - [x] Реализовано состояние и поведение Runtime Supervisor. - [x] Реализован периодический Runtime Scheduler. - [x] Реализован Runtime Recovery Coordinator. - [x] Реализована Runtime Composition с общим Consistency Layer. - [x] Создание Composition не имеет lifecycle- и network-side effects. - [x] Целевые тесты проходят. - [x] Полная регрессия проходит. - [ ] Production lifecycle подключён к приложению — Build 060.25. - [ ] End-to-end reconnect/recovery проверен — Build 060.25–060.26. --- ## 11. Итог Build 060.24 завершает внутреннюю Runtime Recovery Architecture. Проект располагает: - надёжным checkpoint в Consistency Layer; - детерминированным планированием Recovery Window; - изолированными компонентами reconnect, heartbeat, supervisor и scheduler; - координатором последовательного восстановления; - явным composition root с единым состоянием для live и recovery. Следующий этап не должен заново проектировать эти компоненты. Задача [Build 060.25](build_060_25.md) — подключить готовый граф к production WebSocket lifecycle и определить end-to-end порядок запуска, reconnect и recovery.