# Build 060.22 — Acquisition Runtime Service **Engineering Migration Report** --- # Контроль документа | Свойство | Значение | |----------|----------| | Build | 060.22 | | Название | Acquisition Runtime Service | | Статус | Completed | | Проект | Dzentra | | Подсистема | Market Data Acquisition | | Компонент | Acquisition Runtime Service Layer | | Версия | 1.0 | --- # Связанные документы - [Build 060.22 Architecture](build_060_22_architecture.md) — архитектурная спецификация Build. - [Build 060.21 Engineering Migration Report](build_060_21.md) — предыдущий Build. - [Build 060.21 Architecture](build_060_21_architecture.md) — спецификация Runtime Integration Contracts. - [Build 060.20.1 Engineering Migration Report](build_060_20_1.md) — корректирующий Build владения состоянием Trade Stream. --- # Цель Build Build 060.21 завершил формирование контрактного слоя Acquisition Runtime. После предыдущего этапа в системе уже существовали: ```text AcquisitionRuntimeCommand AcquisitionRuntimeEvent AcquisitionRuntimeCommandDispatcherProtocol AcquisitionRuntimeEventPublisherProtocol ``` Также были доступны базовые WebSocket-контракты: ```text WebSocketTransportProtocol WebSocketSessionProtocol WebSocketSubscriptionManagerProtocol ``` Однако Runtime Layer оставался декларативным. Команды описывали инфраструктурные намерения, но в системе отсутствовал производственный компонент, который: - принимал типизированную Runtime-команду; - определял её конкретный тип; - выбирал соответствующую инфраструктурную зависимость; - выполнял одну детерминированную операцию; - сохранял независимость от Trades Feed, Consistency и Recovery. Главной задачей Build 060.22 стало создание первого исполняемого сервисного компонента Acquisition Runtime: ```text AcquisitionRuntimeService ``` Одновременно был введён отдельный публичный контракт сервисного уровня: ```text AcquisitionRuntimeServiceProtocol ``` После завершения Build система получает: - единый сервис исполнения Acquisition Runtime Commands; - детерминированную маршрутизацию всех шести существующих команд; - отдельный публичный Protocol сервиса; - расширенный контракт управления WebSocket-подписками; - полное unit-покрытие нового сервисного слоя; - подтверждённую регрессионную совместимость Runtime, Consistency, Recovery и Feeds. При этом Build сознательно не выполняет интеграцию Runtime Service в корневой Acquisition Service или Trades Feed. Эта задача относится к Build 060.23. --- # Предпосылки К началу настоящего Build Runtime Layer уже обладал полным набором неизменяемых команд: ```text ConnectCommand DisconnectCommand SubscribeCommand UnsubscribeCommand SendTextCommand SendBinaryCommand ``` Также существовали инфраструктурные события: ```text ConnectedEvent DisconnectedEvent ConnectFailedEvent MessageReceivedEvent MessageSentEvent ReconnectStartedEvent ReconnectCompletedEvent ReconnectFailedEvent HeartbeatTimeoutEvent ``` Build 060.21 определил публичные границы передачи команд и событий, но сознательно не создавал исполняемую реализацию. Архитектура выглядела следующим образом: ```text AcquisitionRuntimeCommand │ ▼ AcquisitionRuntimeCommandDispatcherProtocol │ ▼ (реализация отсутствует) ``` Таким образом следующий этап должен был закрыть именно сервисную границу между типизированными командами и существующими инфраструктурными компонентами. --- # Результаты архитектурного аудита Перед началом реализации был выполнен аудит следующих компонентов: ```text src/market_data/acquisition/runtime/websocket_protocol.py src/market_data/acquisition/runtime/runtime_commands.py src/market_data/acquisition/runtime/runtime_events.py src/market_data/acquisition/runtime/transport_messages.py src/market_data/acquisition/runtime/supervisor.py src/market_data/acquisition/runtime/reconnect.py src/market_data/acquisition/runtime/scheduler.py src/market_data/acquisition/runtime/heartbeat.py ``` Дополнительно были проанализированы: ```text src/market_data/acquisition/service.py src/market_data/acquisition/protocol.py src/market_data/acquisition/registry.py ``` а также соответствующие unit-тесты. Аудит подтвердил следующее. ## Runtime orchestration ещё не реализован Файлы: ```text supervisor.py reconnect.py scheduler.py heartbeat.py ``` не содержали производственной реализации. Следовательно Build 060.22 не являлся рефакторингом существующего Runtime Service. Он создавал первую минимальную сервисную реализацию с нуля. --- ## Supervisor преждевременен На раннем этапе рассматривалась возможность реализовать сервис внутри: ```text supervisor.py ``` Данный вариант был отклонён. Supervisor является компонентом более высокого уровня и должен координировать: - Runtime Service; - reconnect; - heartbeat; - scheduler; - lifecycle длительно работающей подсистемы. Поскольку перечисленные механизмы ещё не реализованы, использование Supervisor в Build 060.22 создало бы преждевременную orchestration-логику. Поэтому был введён отдельный тонкий сервис: ```text AcquisitionRuntimeService ``` --- ## Registry для Runtime Service не требуется Существующие Feed Registry предназначены для выбора одной реализации из нескольких зарегистрированных источников. Runtime Service в текущей архитектуре существует в единственном экземпляре и не представляет семейство взаимозаменяемых Feed-реализаций. Создание отдельного Runtime Registry было признано искусственным и исключено из Scope Build. --- ## Корневые Acquisition-файлы не изменяются Аудит подтвердил отсутствие необходимости изменять: ```text src/market_data/acquisition/service.py src/market_data/acquisition/protocol.py src/market_data/acquisition/registry.py ``` Интеграция нового Runtime Service с Acquisition Layer относится к следующему этапу: ```text Build 060.23 — Acquisition Integration ``` --- # Архитектурное решение По результатам аудита была утверждена следующая модель. ```text AcquisitionRuntimeCommand │ ▼ AcquisitionRuntimeServiceProtocol │ ▼ AcquisitionRuntimeService │ ┌─────┼──────────────┐ ▼ ▼ ▼ Session Transport Subscription Manager ``` `AcquisitionRuntimeService` реализует одновременно: ```text AcquisitionRuntimeServiceProtocol AcquisitionRuntimeCommandDispatcherProtocol ``` Сервис не реализует WebSocket самостоятельно. Он только делегирует каждую команду одной заранее определённой инфраструктурной зависимости. --- # Правило уникальности имён файлов Build 060.22 продолжает обязательное архитектурное правило проекта Dzentra: > Во всём репозитории не допускается создание нескольких новых файлов с одинаковым именем независимо от их расположения в каталогах. Единственным служебным исключением остаётся: ```text __init__.py ``` Поэтому Build сознательно не создаёт файлы с общими именами: ```text service.py protocol.py test_service.py ``` Вместо них добавлены уникальные имена: ```text acquisition_runtime_service.py acquisition_runtime_service_protocol.py test_acquisition_runtime_service.py ``` Имена однозначно отражают назначение файлов без необходимости учитывать родительский каталог. Repository-wide поиск показал, что в проекте уже существуют старые файлы с общими именами, созданные до введения данного правила. Build 060.22 не добавляет новых файлов с такими именами и не выполняет ретроспективное переименование существующего рабочего кода вне согласованного Scope. --- # AcquisitionRuntimeServiceProtocol В рамках Build создан новый публичный контракт: ```text AcquisitionRuntimeServiceProtocol ``` Файл: ```text src/market_data/acquisition/runtime/ acquisition_runtime_service_protocol.py ``` Protocol определяет единственную публичную операцию: ```python async def dispatch( command: AcquisitionRuntimeCommand, ) -> None ``` Другие публичные методы не вводятся. В частности отсутствуют: ```text connect() disconnect() subscribe() unsubscribe() send() ``` Все операции Runtime выражаются исключительно через типизированные команды. Это исключает появление второго параллельного API для одной и той же инфраструктурной операции. --- # Почему Service Protocol существует отдельно В Build 060.21 уже был добавлен: ```text AcquisitionRuntimeCommandDispatcherProtocol ``` Он определяет инфраструктурную способность принимать команды. Build 060.22 дополнительно вводит: ```text AcquisitionRuntimeServiceProtocol ``` Данный контракт фиксирует отдельную сервисную границу Runtime Layer. Такое разделение позволяет последующим компонентам зависеть именно от сервисной абстракции, а не от конкретной реализации. В будущем под тем же Protocol могут существовать различные реализации: ```text Simple Acquisition Runtime Service Queued Acquisition Runtime Service Supervised Acquisition Runtime Service ``` Build 060.22 реализует только минимальный прямой вариант. --- # AcquisitionRuntimeService Главным production-компонентом Build становится: ```text AcquisitionRuntimeService ``` Файл: ```text src/market_data/acquisition/runtime/ acquisition_runtime_service.py ``` Сервис является тонким stateless-координатором. Он: - принимает одну типизированную команду; - определяет её конкретный тип; - вызывает соответствующую инфраструктурную зависимость; - завершает выполнение после завершения делегированной операции. Сервис не: - создаёт фоновые задачи; - хранит очередь команд; - выполняет retries; - управляет reconnect; - запускает heartbeat; - хранит подписки; - обрабатывает Trade; - вызывает Consistency; - вызывает Recovery. --- # Зависимости сервиса Все зависимости передаются через конструктор. ```text AcquisitionRuntimeService │ ├── WebSocketSessionProtocol ├── WebSocketTransportProtocol ├── WebSocketSubscriptionManagerProtocol └── AcquisitionRuntimeEventPublisherProtocol ``` Сервис не создаёт зависимости самостоятельно. Создание графа объектов относится к Composition Root и не входит в Scope Build 060.22. --- # Почему Event Publisher внедрён, но пока не используется Конструктор Runtime Service принимает: ```text AcquisitionRuntimeEventPublisherProtocol ``` Однако Build 060.22 не публикует события после выполнения команд. Это осознанное решение. Publisher включён в окончательную форму конструктора, чтобы последующие Build могли подключить публикацию событий без изменения публичной композиции сервиса. При этом преждевременная event-orchestration не добавляется. Unit-тест отдельно подтверждает, что Publisher пока остаётся неиспользованным. --- # Расширение WebSocketSubscriptionManagerProtocol Во время реализации было обнаружено расхождение между первоначальной архитектурной матрицей и фактическим контрактом Subscription Manager. До Build Protocol содержал только: ```python restore_subscriptions() clear_subscriptions() ``` Но Runtime Service должен исполнять: ```text SubscribeCommand UnsubscribeCommand ``` Следовательно существующий контракт был недостаточен для детерминированной маршрутизации. В Build `WebSocketSubscriptionManagerProtocol` локально расширен методами: ```python async def subscribe( subscription_key: str, message: AcquisitionSubscriptionMessage, ) -> None ``` и: ```python async def unsubscribe( subscription_key: str, message: AcquisitionSubscriptionMessage, ) -> None ``` Существующие методы: ```text restore_subscriptions() clear_subscriptions() ``` полностью сохранены. Расширение является обратно совместимым с точки зрения существующих моделей данных и не изменяет поведение уже реализованных компонентов. --- # AcquisitionSubscriptionMessage Для операций подписки введён типовой alias: ```text AcquisitionSubscriptionMessage ``` Он объединяет: ```text TransportTextMessage TransportBinaryMessage ``` Благодаря этому Subscription Manager принимает не произвольные `str | bytes`, а уже существующие неизменяемые модели транспортных сообщений. Это обеспечивает соответствие моделям: ```text SubscribeCommand UnsubscribeCommand ``` и исключает повторную интерпретацию payload внутри Runtime Service. --- # Официальная матрица маршрутизации Build 060.22 реализует следующую детерминированную таблицу. | Runtime Command | Действие | |-----------------|----------| | `ConnectCommand` | `WebSocketSessionProtocol.start()` | | `DisconnectCommand` | `WebSocketSessionProtocol.stop()` | | `SubscribeCommand` | `WebSocketSubscriptionManagerProtocol.subscribe(...)` | | `UnsubscribeCommand` | `WebSocketSubscriptionManagerProtocol.unsubscribe(...)` | | `SendTextCommand` | `WebSocketTransportProtocol.send(message.payload)` | | `SendBinaryCommand` | `WebSocketTransportProtocol.send(message.payload)` | Каждый тип команды имеет ровно один маршрут. Сервис не выполняет дополнительные действия и не вызывает несколько зависимостей для одной команды. --- # ConnectCommand Маршрут: ```text ConnectCommand │ ▼ WebSocketSessionProtocol.start() ``` Runtime Service не вызывает `transport.connect()` напрямую. Жизненный цикл соединения принадлежит Session. --- # DisconnectCommand Маршрут: ```text DisconnectCommand │ ▼ WebSocketSessionProtocol.stop() ``` Runtime Service не вызывает `transport.disconnect()` напрямую. Завершение сессии остаётся обязанностью Session. --- # SubscribeCommand Маршрут: ```text SubscribeCommand │ ▼ WebSocketSubscriptionManagerProtocol.subscribe( subscription_key, message, ) ``` Runtime Service передаёт исходные значения без изменения. Сервис не хранит список подписок и не сериализует сообщение. --- # UnsubscribeCommand Маршрут: ```text UnsubscribeCommand │ ▼ WebSocketSubscriptionManagerProtocol.unsubscribe( subscription_key, message, ) ``` Сервис не изменяет внутреннее состояние Subscription Manager самостоятельно. --- # SendTextCommand Маршрут: ```text SendTextCommand │ ▼ WebSocketTransportProtocol.send(str) ``` В Transport передаётся: ```text command.message.payload ``` Runtime Service не вводит отдельный метод `send_text()` и не расширяет Transport Protocol без необходимости. --- # SendBinaryCommand Маршрут: ```text SendBinaryCommand │ ▼ WebSocketTransportProtocol.send(bytes) ``` В Transport передаётся бинарный payload без преобразования. Существующий единый метод: ```python send(message: str | bytes) ``` полностью покрывает оба типа сообщений. --- # Обработка неизвестной команды Тип `AcquisitionRuntimeCommand` ограничивает штатный публичный вход сервиса. Однако Runtime Service также содержит защитную ветку для произвольного неподдерживаемого объекта. В этом случае генерируется: ```text TypeError ``` с диагностическим сообщением, включающим имя фактического типа. Это гарантирует, что появление новой Runtime-команды потребует явного обновления маршрутизации и не останется незамеченным. --- # Обработка исключений Build не вводит собственную иерархию ошибок Runtime Service. Любое исключение инфраструктурной зависимости распространяется вызывающему компоненту без обёртки. Например: ```text ConnectCommand │ ▼ AcquisitionRuntimeService │ ▼ WebSocketSession.start() │ ▼ RuntimeError ``` Сервис не преобразует ошибку в общий `RuntimeDispatchError`. Такое решение сохраняет: - исходный тип исключения; - исходное диагностическое сообщение; - прозрачность инфраструктурного поведения; - возможность точной обработки на более высоком уровне. --- # Stateless-архитектура сервиса `AcquisitionRuntimeService` не хранит собственное операционное состояние. Внутри объекта сохраняются только внедрённые зависимости. Сервис не хранит: - состояние соединения; - выполненные команды; - активные подписки; - историю отправленных сообщений; - reconnect attempts; - Recovery state; - Consistency state. Все перечисленные данные принадлежат специализированным компонентам. --- # Изменённые файлы ## Новый Service Protocol ```text src/market_data/acquisition/runtime/ acquisition_runtime_service_protocol.py ``` Добавлен: ```text AcquisitionRuntimeServiceProtocol ``` Protocol содержит единственный асинхронный метод `dispatch()`. --- ## Новый Runtime Service ```text src/market_data/acquisition/runtime/ acquisition_runtime_service.py ``` Добавлен: ```text AcquisitionRuntimeService ``` Класс реализует: ```text AcquisitionRuntimeServiceProtocol AcquisitionRuntimeCommandDispatcherProtocol ``` и маршрутизирует все шесть Runtime Commands. --- ## Расширение WebSocket Protocol Layer ```text src/market_data/acquisition/runtime/websocket_protocol.py ``` Добавлены: ```text AcquisitionSubscriptionMessage ``` и методы: ```text WebSocketSubscriptionManagerProtocol.subscribe() WebSocketSubscriptionManagerProtocol.unsubscribe() ``` Существующие Protocol и alias сохранены. --- ## Unit-тест Runtime Service ```text tests/unit/market_data/acquisition/runtime/ test_acquisition_runtime_service.py ``` Добавлены тестовые реализации: - `FakeSession`; - `FakeTransport`; - `FakeSubscriptionManager`; - `FakeEventPublisher`. Проверена маршрутизация всех поддерживаемых команд и распространение исключений. --- ## Обновление теста WebSocket Protocol ```text tests/unit/market_data/acquisition/runtime/ test_websocket_protocol.py ``` `FakeWebSocketSubscriptionManager` расширен методами: ```text subscribe() unsubscribe() ``` Также добавлена негативная проверка неполной реализации обновлённого Protocol. --- # Файлы, которые не изменялись Build не изменяет: ```text src/market_data/acquisition/runtime/runtime_commands.py src/market_data/acquisition/runtime/runtime_events.py src/market_data/acquisition/runtime/transport_messages.py src/market_data/acquisition/runtime/supervisor.py src/market_data/acquisition/runtime/reconnect.py src/market_data/acquisition/runtime/scheduler.py src/market_data/acquisition/runtime/heartbeat.py ``` Также не изменяются: - корневой Acquisition Service; - Acquisition Registry; - Acquisition Protocol; - Trades Feed; - Quotes Feed; - Candles Feed; - Trade Stream Consistency; - Trade Stream State Store; - Trade Recovery; - WebSocket adapters; - каноническая модель `Trade`. --- # Unit-тестирование AcquisitionRuntimeService Новый сервис покрыт отдельным набором unit-тестов. Проверены следующие сценарии: - сервис соответствует `AcquisitionRuntimeServiceProtocol`; - сервис соответствует `AcquisitionRuntimeCommandDispatcherProtocol`; - `ConnectCommand` вызывает только `session.start()`; - `DisconnectCommand` вызывает только `session.stop()`; - `SubscribeCommand` передаёт исходные `subscription_key` и message; - `UnsubscribeCommand` передаёт исходные `subscription_key` и message; - `SendTextCommand` передаёт строковый payload в `transport.send()`; - `SendBinaryCommand` передаёт бинарный payload в `transport.send()`; - Event Publisher не используется преждевременно; - неизвестная команда приводит к `TypeError`; - исключение Session распространяется без изменения. Результат: ```text 11 passed ``` --- # Unit-тестирование WebSocket Protocol После расширения Subscription Manager выполнен локальный прогон обновлённого Protocol Layer. Проверены: - полная реализация Transport; - полная реализация Session; - полная реализация Subscription Manager; - Command Dispatcher; - Event Publisher; - отрицательные сценарии неполных реализаций. Результат: ```text 9 passed ``` --- # Регрессионное тестирование Runtime Layer После добавления Runtime Service выполнен полный прогон: ```text tests/unit/market_data/acquisition/runtime ``` Проверены: - Acquisition Runtime Service; - Runtime Commands; - Runtime Events; - Transport Messages; - WebSocket Protocol Layer. Результат: ```text 46 passed ``` Это подтверждает, что новый сервисный слой не нарушил существующее поведение Runtime. --- # Расширенное регрессионное тестирование Для подтверждения архитектурной изоляции выполнен совместный прогон: ```text Runtime Consistency Recovery Feeds ``` Команда охватывала каталоги: ```text tests/unit/market_data/acquisition/runtime tests/unit/market_data/acquisition/consistency tests/unit/market_data/acquisition/recovery tests/unit/market_data/acquisition/feeds ``` Результат: ```text 180 passed ``` Тем самым подтверждено: - Runtime Service работает корректно; - обновление Subscription Manager не нарушило Protocol Layer; - Trade Stream Consistency не затронута; - Trade Stream State Store не затронут; - Trade Recovery не затронута; - существующие Feed продолжают работать без изменений; - преждевременная интеграция Build 060.23 не произошла. --- # Обратная совместимость Build сохраняет функциональное поведение существующих подсистем. Не изменились: - Runtime Command models; - Runtime Event models; - Transport Message models; - WebSocket Transport API; - WebSocket Session API; - Canonical Trade Model; - Trades Feed API; - Consistency API; - Recovery API; - Feed Registry; - корневые Acquisition Services. Единственное расширение существующего контракта относится к `WebSocketSubscriptionManagerProtocol` и добавляет операции, уже необходимые существующим `SubscribeCommand` и `UnsubscribeCommand`. --- # Производительность Runtime Service выполняет конечную цепочку `isinstance`-проверок по шести типам команд и один делегированный асинхронный вызов. Сервис не вводит: - очереди; - дополнительные копии payload; - сериализацию; - фоновые задачи; - блокировки; - retries; - хранение истории. Следовательно накладные расходы ограничены постоянным временем: ```text O(1) ``` для каждой команды. Основная задержка полностью определяется вызываемой инфраструктурной зависимостью. --- # Подтверждённые архитектурные инварианты ## Runtime Service не знает предметную область Сервис не импортирует: ```text Trade TradesFeed TradeStreamConsistencyProtocol TradeRecoveryProtocol TradeStreamStateStore ``` --- ## Runtime Service stateless Сервис хранит только ссылки на внедрённые зависимости. --- ## Одна команда имеет один маршрут Ни одна команда не выполняет несколько инфраструктурных операций. --- ## Session владеет lifecycle соединения `ConnectCommand` и `DisconnectCommand` делегируются Session, а не Transport напрямую. --- ## Subscription Manager владеет подписками Runtime Service не хранит список подписок и не управляет их состоянием самостоятельно. --- ## Transport владеет отправкой payload Text и Binary payload передаются через существующий единый метод `send()`. --- ## Event publication не реализована преждевременно Publisher внедрён, но не используется до отдельного этапа интеграции событий. --- ## Consistency и Recovery не зависят от Runtime Service Новые импорты и зависимости в данных подсистемах отсутствуют. --- ## Уникальность новых имён файлов соблюдена Build не добавляет новых `service.py`, `protocol.py` или `test_service.py`. --- # Что не входит в Scope Build Настоящий Build сознательно не реализует: - интеграцию Runtime Service в корневой Acquisition Service; - интеграцию Runtime Service с Trades Feed; - Composition Root; - Runtime Supervisor; - Reconnect orchestration; - Heartbeat; - Scheduler; - очередь команд; - background worker; - публикацию Runtime Events; - обработку входящих WebSocket-сообщений; - маршрутизацию сообщений к Trade Adapter; - запуск Trade Stream Consistency; - автоматический Trade Recovery; - восстановление подписок после reconnect; - реальный WebSocket transport implementation; - сохранение Runtime-состояния между перезапусками. Отсутствие перечисленных компонентов является осознанной границей Build и не рассматривается как незавершённость. --- # Архитектурное значение Build Build 060.22 является первым этапом серии Runtime, добавляющим исполняемое поведение. До него Runtime Layer содержал: - модели сообщений; - команды; - события; - Protocol. После завершения Build появляется единая точка выполнения команд: ```text AcquisitionRuntimeService ``` Это превращает Runtime из исключительно декларативного слоя в минимальную работающую сервисную подсистему. При этом сервис остаётся полностью изолированным от предметной области и готов к последующей интеграции без изменения уже реализованных Consistency, Recovery и Feed. --- # Связь с последующими Build > **Ретроспективное уточнение 060.30.5 (2026-08-03).** > > Названия ниже сохраняют план на момент завершения 060.22. Фактически > [060.24](build_060_24.md) завершил внутреннюю Runtime Recovery, > [060.25](build_060_25.md) — Production Runtime Integration, > [060.26](build_060_26.md) — Integration & Regression, > [060.27](build_060_27.md)–[060.29](build_060_29.md) — > Storage/Checkpoint/Access, а Final Documentation выполняется в > [060.30](build_060_30_architecture.md). > Актуальная последовательность: > [Master Roadmap](../roadmap/master-roadmap.md). > После 060.30 утверждён только 061.00; номера следующих Feed-веток ещё > не назначены. ## Build 060.23 — Acquisition Integration Новый Runtime Service будет подключён к верхнему уровню Acquisition и станет доступен через утверждённую композицию зависимостей. --- ## Build 060.24 — Reconnect & Runtime Recovery На основе Runtime Service будут построены: - reconnect orchestration; - resubscribe; - восстановление Runtime-потока; - вызов Trade Recovery после нарушения непрерывности. --- ## Build 060.25 — Integration & Regression Будет выполнена полная проверка взаимодействия Runtime, Acquisition, Trades Feed, Consistency и Recovery. --- # Заключение Build 060.22 завершает создание минимального исполняемого сервисного слоя Acquisition Runtime. В рамках этапа были реализованы: ```text AcquisitionRuntimeServiceProtocol AcquisitionRuntimeService ``` а также расширен: ```text WebSocketSubscriptionManagerProtocol ``` для поддержки существующих команд подписки и отмены подписки. Runtime Service детерминированно маршрутизирует все шесть команд к соответствующим инфраструктурным зависимостям и не содержит знаний о рыночных данных. Архитектура сохраняет строгие границы: - Session управляет lifecycle; - Transport отправляет payload; - Subscription Manager управляет подписками; - Runtime Service только координирует; - Consistency и Recovery остаются независимыми. Все изменения подтверждены локальными и расширенными regression-тестами. --- # Итог Build После завершения Build 060.22 система обладает следующими возможностями. ✓ Создан отдельный публичный `AcquisitionRuntimeServiceProtocol`. ✓ Реализован `AcquisitionRuntimeService`. ✓ Поддерживаются все шесть существующих Acquisition Runtime Commands. ✓ Каждая команда имеет один детерминированный маршрут. ✓ `WebSocketSubscriptionManagerProtocol` поддерживает subscribe и unsubscribe. ✓ Text и Binary payload отправляются через существующий Transport API. ✓ Исключения инфраструктурных зависимостей распространяются без изменения. ✓ Event Publisher не используется преждевременно. ✓ Все новые файлы имеют уникальные имена. ✓ Runtime regression завершён результатом `46 passed`. ✓ Расширенный regression завершён результатом `180 passed`. Build **060.22 — Acquisition Runtime Service** считается полностью завершённым и готовым к фиксации в Git.