Build 060.24: implement Runtime Recovery Architecture

This commit is contained in:
2026-07-30 00:17:12 +03:00
parent ee8765b716
commit c142145361
21 changed files with 7475 additions and 5 deletions

View File

@@ -0,0 +1,552 @@
# 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.md` — итоговая архитектурная спецификация Build.
- `build_060_23.md` — Trade Stream Acquisition Integration.
- `build_060_23_architecture.md` — архитектура интеграции Trade Stream Acquisition.
- `build_060_22.md` — Runtime Service Integration.
- `build_060_20_1.md` — Trade Stream State Ownership Alignment.
- `build_060_19.md` — Trade Recovery.
- `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.25060.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.25060.26.
---
## 11. Итог
Build 060.24 завершает внутреннюю Runtime Recovery Architecture.
Проект располагает:
- надёжным checkpoint в Consistency Layer;
- детерминированным планированием Recovery Window;
- изолированными компонентами reconnect, heartbeat, supervisor
и scheduler;
- координатором последовательного восстановления;
- явным composition root с единым состоянием для live и recovery.
Следующий этап не должен заново проектировать эти компоненты.
Задача Build 060.25 — подключить готовый граф к production WebSocket
lifecycle и определить end-to-end порядок запуска, reconnect и recovery.

File diff suppressed because it is too large Load Diff