Files
dzentra_bot/docs/migrations/build_011.md

1602 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build 011 — Instrument Feed Registry
**Статус:** Завершён
**Подсистема:** `market_data/acquisition`
**Область:** Instrument Reference Data
**Тип изменения:** Изолированное добавление Registry для регистрации и получения Instrument Feed без подключения к production runtime
**Результат полного набора тестов:** `167 passed`
---
## 1. Цель Build 011
Цель Build 011 — реализовать Registry для регистрации и получения доступных Instrument Feed по идентификатору источника.
Реализован класс:
```text
InstrumentFeedRegistry
```
Его архитектурная граница:
```text
source_name
InstrumentFeedRegistry
InstrumentFeedProtocol
```
Registry позволяет:
```text
зарегистрировать Feed;
получить Feed по имени источника;
запретить неявную повторную регистрацию;
явно сообщить об отсутствии запрошенного Feed.
```
Build 011 не хранит модели `Instrument`, не выполняет acquisition, не создаёт кэш и не подключается к существующему `ExchangeService`.
---
## 2. Почему Build 011 выполняется именно сейчас
До начала Build 011 были завершены:
```text
Build 001 — внутренняя модель Instrument Reference Data
Build 002 — raw-модели ответа Dzengi
Build 003 — структурная валидация exchangeInfo
Build 004 — parser exchangeInfo
Build 005 — value validation
Build 006 — mapper Dzengi → Instrument
Build 007 — Protocol и Exceptions
Build 008 — Dzengi REST Adapter
Build 009 — Instrument Handler
Build 010 — Instrument Feed
```
После Build 010 уже существовала полная техническая цепочка:
```text
Dzengi REST API
DzengiInstrumentDocumentSource
InstrumentFeed
DzengiInstrumentDocumentHandler
tuple[Instrument, ...]
```
Однако будущему `Acquisition Service` ещё требовался механизм получения нужного Feed без прямой зависимости от конкретной реализации.
Build 011 создаёт эту границу:
```text
Acquisition Service
source_name
InstrumentFeedRegistry
InstrumentFeedProtocol
```
---
## 3. Архитектурное уточнение ответственности Registry
Registry в Build 011 не является хранилищем актуального справочника инструментов.
Он не хранит:
```text
tuple[Instrument, ...];
последний успешный справочник;
предыдущий snapshot;
TTL;
время обновления;
возраст данных;
последнюю ошибку;
индекс символов;
legacy ExchangeSymbol.
```
Его единственная предметная ответственность:
```text
source_name
InstrumentFeedProtocol
```
Хранение и кэширование актуальных данных относятся к другим архитектурным слоям и будущим Build:
```text
Build 019 — подготовка переноса кэша в Storage
Build 020 — перенос кэша
```
---
## 4. Изменённые файлы
В рамках Build 011 изменены:
```text
app/src/market_data/acquisition/exceptions.py
app/src/market_data/acquisition/registry.py
```
Создан тестовый файл:
```text
app/tests/unit/market_data/acquisition/test_registry.py
```
Другие production-файлы не изменялись.
---
## 5. Реализованный InstrumentFeedRegistry
В файле:
```text
app/src/market_data/acquisition/registry.py
```
реализован класс:
```python
class InstrumentFeedRegistry:
...
```
Registry предоставляет два публичных метода:
```python
def register(
self,
source_name: str,
feed: InstrumentFeedProtocol,
) -> None:
...
```
и:
```python
def get(
self,
source_name: str,
) -> InstrumentFeedProtocol:
...
```
Минимальный публичный контракт:
```text
register(source_name, feed)
регистрация Feed
get(source_name)
получение зарегистрированного Feed
```
---
## 6. Внутреннее хранение
Registry использует внутренний типизированный словарь:
```python
dict[str, InstrumentFeedProtocol]
```
Архитектурная схема:
```text
"dzengi"
InstrumentFeedProtocol
```
Пример:
```text
{
"dzengi": <InstrumentFeedProtocol>
}
```
Внутренний словарь:
```text
не возвращается наружу;
не содержит Instrument;
не является предметным кэшем;
не содержит результатов Feed;
используется только как индекс зарегистрированных Feed.
```
---
## 7. Регистрация Feed
Метод:
```python
register(
source_name: str,
feed: InstrumentFeedProtocol,
) -> None
```
выполняет следующую последовательность:
```text
получить source_name
удалить внешние пробелы
проверить непустое имя
проверить соответствие InstrumentFeedProtocol
проверить отсутствие существующей регистрации
сохранить Feed
```
Пример:
```python
registry.register(
"dzengi",
feed,
)
```
После этого:
```python
registry.get("dzengi")
```
возвращает тот же объект `feed`.
---
## 8. Получение Feed
Метод:
```python
get(
source_name: str,
) -> InstrumentFeedProtocol
```
выполняет:
```text
получить source_name
удалить внешние пробелы
проверить непустое имя
найти зарегистрированный Feed
вернуть тот же объект Feed
```
Если Feed отсутствует, выбрасывается:
```text
InstrumentFeedRegistryError
```
Registry не создаёт Feed автоматически и не пытается использовать default source.
---
## 9. Сохранение identity Feed
Registry сохраняет и возвращает исходный объект Feed без:
```text
копирования;
оборачивания;
создания proxy;
создания нового Feed;
изменения объекта.
```
Если зарегистрирован:
```python
feed = StubInstrumentFeed()
registry.register("dzengi", feed)
```
то:
```python
registry.get("dzengi") is feed
```
равно:
```text
True
```
Это подтверждено unit-тестами.
---
## 10. Проверка InstrumentFeedProtocol
Перед регистрацией выполняется:
```python
isinstance(feed, InstrumentFeedProtocol)
```
Это возможно благодаря тому, что `InstrumentFeedProtocol` объявлен как runtime-checkable Protocol.
Архитектурная проверка:
```text
объект
соответствует InstrumentFeedProtocol?
├── да → регистрация разрешена
└── нет → InstrumentFeedRegistryError
```
Registry не требует явного наследования от Protocol.
Используется structural typing:
```text
если объект реализует требуемый контракт,
он соответствует Protocol.
```
---
## 11. Registry не вызывает Feed
При выполнении:
```python
registry.register("dzengi", feed)
```
не вызывается:
```python
feed.load_instruments()
```
При выполнении:
```python
registry.get("dzengi")
```
также не вызывается:
```python
feed.load_instruments()
```
Registry только хранит и возвращает Feed.
Цепочка:
```text
register()
сохранить ссылку на Feed
get()
вернуть ссылку на Feed
```
Отсутствует:
```text
load_instruments()
```
Это подтверждено отдельными unit-тестами.
---
## 12. Нормализация имени источника
Registry выполняет только:
```python
source_name.strip()
```
Пример:
```text
" dzengi "
"dzengi"
```
Поэтому после:
```python
registry.register(" dzengi ", feed)
```
оба вызова:
```python
registry.get("dzengi")
registry.get(" dzengi ")
```
возвращают тот же Feed.
---
## 13. Что Registry не делает с именем источника
Registry намеренно не выполняет:
```text
lower()
casefold()
replace()
alias resolution
automatic source mapping
```
Поэтому:
```text
"dzengi"
```
и:
```text
"DZENGI"
```
являются разными ключами.
Можно одновременно зарегистрировать:
```python
registry.register("dzengi", first_feed)
registry.register("DZENGI", second_feed)
```
После этого:
```text
get("dzengi")
first_feed
get("DZENGI")
second_feed
```
Такое поведение исключает скрытую нормализацию без утверждённого контракта.
---
## 14. Запрет пустого имени источника
Registry отклоняет:
```text
""
" "
" "
"\t"
"\n"
```
После:
```python
source_name.strip()
```
такие значения становятся пустыми.
Выбрасывается:
```text
InstrumentFeedRegistryError
```
с сообщением:
```text
Имя источника Instrument Feed не должно быть пустым.
```
Проверка действует как для:
```text
register()
```
так и для:
```text
get()
```
---
## 15. Запрет повторной регистрации
Следующая последовательность запрещена:
```python
registry.register("dzengi", first_feed)
registry.register("dzengi", second_feed)
```
Вторая операция выбрасывает:
```text
InstrumentFeedRegistryError
```
с диагностикой:
```text
Instrument Feed для источника 'dzengi' уже зарегистрирован.
```
Registry не выполняет молчаливую замену.
---
## 16. Почему молчаливая замена запрещена
Молчаливая операция:
```text
existing Feed
register same source name
new Feed silently replaces old Feed
```
могла бы незаметно изменить production acquisition pipeline.
Поэтому используется fail-fast поведение:
```text
duplicate source name
InstrumentFeedRegistryError
```
Если в будущем понадобится контролируемая замена Feed, она должна быть реализована отдельным явно определённым контрактом.
В Build 011 такой контракт не нужен.
---
## 17. Повторная регистрация после нормализации
Проверка duplicate выполняется после:
```python
source_name.strip()
```
Поэтому:
```python
registry.register("dzengi", first_feed)
registry.register(" dzengi ", second_feed)
```
считается повторной регистрацией одного и того же источника.
Вторая операция завершается:
```text
InstrumentFeedRegistryError
```
Исходный Feed при этом сохраняется.
---
## 18. Отсутствие молчаливой замены Feed
После:
```python
registry.register("dzengi", first_feed)
```
и неуспешной попытки:
```python
registry.register("dzengi", second_feed)
```
результат:
```python
registry.get("dzengi") is first_feed
```
остаётся:
```text
True
```
Таким образом, ошибочная повторная регистрация не изменяет состояние Registry.
---
## 19. Поведение для отсутствующего источника
Если выполнить:
```python
registry.get("dzengi")
```
до регистрации Feed, выбрасывается:
```text
InstrumentFeedRegistryError
```
с диагностикой:
```text
Instrument Feed для источника 'dzengi' не зарегистрирован.
```
Registry не:
```text
возвращает None;
создаёт Feed автоматически;
выбирает default Feed;
выполняет fallback на другой источник.
```
Отсутствие Feed является явной ошибкой Registry.
---
## 20. Новая ошибка InstrumentFeedRegistryError
В файле:
```text
app/src/market_data/acquisition/exceptions.py
```
добавлен класс:
```python
class InstrumentFeedRegistryError(MarketDataAcquisitionError):
pass
```
Иерархия acquisition-ошибок после Build 011:
```text
MarketDataAcquisitionError
├── InstrumentReferenceTransportError
├── InstrumentReferenceSchemaError
├── InstrumentReferenceParseError
├── InstrumentReferenceValueError
├── InstrumentReferenceMappingError
└── InstrumentFeedRegistryError
```
Ошибка используется для:
```text
пустого имени источника;
объекта, не соответствующего InstrumentFeedProtocol;
повторной регистрации;
запроса отсутствующего Feed.
```
---
## 21. Почему не созданы отдельные Registry exceptions
В Build 011 намеренно не добавлены:
```text
DuplicateInstrumentFeedError
InstrumentFeedNotFoundError
InvalidInstrumentFeedNameError
InvalidInstrumentFeedError
```
На текущем этапе для них нет отдельных алгоритмов обработки.
Все ошибки относятся к одной архитектурной категории:
```text
Instrument Feed Registry error
```
Поэтому используется один класс:
```text
InstrumentFeedRegistryError
```
Дополнительная детализация исключений без реальной необходимости только усложнила бы контракт.
---
## 22. Dependency Injection
Registry не создаёт Feed самостоятельно.
Отсутствует:
```python
self._feed = InstrumentFeed(...)
```
Также Registry не создаёт:
```text
DzengiInstrumentDocumentSource
DzengiInstrumentDocumentHandler
ExchangeRestClient
```
Правильная будущая композиция:
```text
DzengiInstrumentDocumentSource
+
DzengiInstrumentDocumentHandler
InstrumentFeed
registry.register("dzengi", feed)
```
Composition остаётся явной и находится за пределами Registry.
---
## 23. Почему Registry хранит Feed, а не Source и Handler отдельно
К моменту Build 011 уже существует архитектурная capability:
```text
InstrumentFeedProtocol
```
Feed инкапсулирует взаимодействие:
```text
InstrumentDocumentSource
InstrumentDocumentHandler
```
Если Registry начал бы отдельно хранить:
```text
Source
Handler
```
ему пришлось бы знать, как их соединять.
Это нарушило бы границу Build 010.
Правильная схема:
```text
Source
+
Handler
InstrumentFeed
InstrumentFeedRegistry
```
Registry работает только с готовым:
```text
InstrumentFeedProtocol
```
---
## 24. Что Registry не хранит
Внутри Registry отсутствуют:
```text
Instrument;
tuple[Instrument, ...];
результат load_instruments();
последний успешный справочник;
предыдущий справочник;
snapshot;
timestamp;
TTL;
cache age;
последняя ошибка Feed;
legacy ExchangeSymbol.
```
Registry хранит только:
```text
dict[str, InstrumentFeedProtocol]
```
---
## 25. Что Registry не делает
Build 011 сознательно не выполняет:
```text
REST-запросы;
получение exchangeInfo;
schema validation;
parsing;
value validation;
mapping;
вызов load_instruments();
retry;
backoff;
кэширование;
хранение Instrument;
сравнение snapshot;
фильтрацию инструментов;
сортировку инструментов;
дедупликацию инструментов;
нормализацию торговых символов;
создание Source;
создание Handler;
создание Feed;
создание Acquisition Service;
создание compatibility mapper;
изменение ExchangeService;
изменение AutoTrade;
изменение Telegram UI;
изменение trading runtime.
```
Единственная ответственность:
```text
source_name
InstrumentFeedProtocol
```
---
## 26. Реализованные тестовые сценарии
Создан файл:
```text
app/tests/unit/market_data/acquisition/test_registry.py
```
Фактически выполнено:
```text
25 tests
```
Проверены следующие сценарии:
1. регистрация и получение Feed;
2. сохранение identity Feed;
3. соответствие объекта `InstrumentFeedProtocol`;
4. несколько Feed под разными именами;
5. удаление внешних пробелов из имени;
6. запрет пустой строки при регистрации;
7. запрет строки из пробелов при регистрации;
8. запрет tab/newline при регистрации;
9. запрет пустой строки при `get()`;
10. запрет строки из пробелов при `get()`;
11. запрет tab/newline при `get()`;
12. запрет повторной регистрации;
13. отсутствие замены исходного Feed при duplicate;
14. duplicate после нормализации внешних пробелов;
15. сохранение case-sensitive поведения;
16. ошибка при запросе отсутствующего Feed;
17. запрет объекта без `InstrumentFeedProtocol`;
18. отсутствие вызова Feed при регистрации;
19. отсутствие вызова Feed при получении;
20. хранение Feed, а не результата `Instrument`;
21. наследование `InstrumentFeedRegistryError` от `MarketDataAcquisitionError`.
Часть сценариев реализована через параметризацию, поэтому фактическое количество выполненных тестовых случаев составляет:
```text
25
```
---
## 27. Проверка регистрации и получения Feed
Тест подтверждает:
```python
registry = InstrumentFeedRegistry()
feed = StubInstrumentFeed()
registry.register("dzengi", feed)
result = registry.get("dzengi")
assert result is feed
```
Это доказывает:
```text
Feed зарегистрирован;
Feed доступен по source_name;
identity объекта сохранена.
```
---
## 28. Проверка нескольких источников
Registry поддерживает несколько независимых записей:
```text
"dzengi"
dzengi_feed
"secondary"
secondary_feed
```
Тест подтверждает:
```python
assert registry.get("dzengi") is dzengi_feed
assert registry.get("secondary") is secondary_feed
```
Registry не имеет встроенного ограничения на один источник.
---
## 29. Проверка отсутствия вызова Feed
Тестовый Feed содержит счётчик:
```python
self.load_call_count = 0
```
Метод:
```python
load_instruments()
```
увеличивает счётчик.
После:
```python
registry.register("dzengi", feed)
```
подтверждено:
```python
assert feed.load_call_count == 0
```
После:
```python
registry.get("dzengi")
```
также подтверждено:
```python
assert feed.load_call_count == 0
```
Таким образом, Registry не запускает acquisition pipeline.
---
## 30. Проверка хранения Feed, а не результата Feed
Тест создаёт:
```python
instruments = (
_instrument(),
)
```
и Feed:
```python
feed = StubInstrumentFeed(
instruments=instruments,
)
```
После регистрации:
```python
registered_feed = registry.get("dzengi")
```
подтверждено:
```python
assert registered_feed is feed
assert registered_feed is not instruments
```
Registry хранит сам Feed, а не:
```text
tuple[Instrument, ...]
```
---
## 31. Выполненные проверки
### Проверка 1 — unit-тесты Registry
Команда:
```bash
python -m pytest \
tests/unit/market_data/acquisition/test_registry.py \
-q
```
Результат:
```text
......................... [100%]
25 passed in 0.02s
```
Статус:
```text
PASSED
```
---
### Проверка 2 — Python compilation
Команда:
```bash
python -m py_compile \
src/market_data/acquisition/exceptions.py \
src/market_data/acquisition/registry.py \
tests/unit/market_data/acquisition/test_registry.py
```
Результат:
```text
Команда завершилась без ошибок и без вывода.
```
Статус:
```text
PASSED
```
---
### Проверка 3 — полный набор тестов проекта
Команда:
```bash
python -m pytest -q
```
Результат:
```text
....................................................................................................................................................................... [100%]
167 passed in 0.09s
```
Статус:
```text
PASSED
```
---
### Проверка 4 — отсутствие преждевременной production-интеграции
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "InstrumentFeedRegistry|InstrumentFeedRegistryError" \
src tests
```
Полученные production-использования находятся только в:
```text
src/market_data/acquisition/registry.py
src/market_data/acquisition/exceptions.py
```
Остальные использования находятся исключительно в:
```text
tests/unit/market_data/acquisition/test_registry.py
```
Не обнаружено подключения к:
```text
Acquisition Service
ExchangeService
Telegram UI
AutoTrade
Trading runtime
другим production-потребителям
```
Статус:
```text
PASSED
```
---
## 32. Архитектура после Build 011
После завершения Build 011 новая часть подсистемы имеет следующую структуру:
```text
market_data/
└── acquisition/
├── exceptions.py
│ ├── MarketDataAcquisitionError
│ ├── InstrumentReferenceTransportError
│ ├── InstrumentReferenceSchemaError
│ ├── InstrumentReferenceParseError
│ ├── InstrumentReferenceValueError
│ ├── InstrumentReferenceMappingError
│ └── InstrumentFeedRegistryError
├── protocol.py
│ ├── InstrumentDocumentSource
│ ├── InstrumentDocumentHandler
│ └── InstrumentFeedProtocol
├── registry.py
│ └── InstrumentFeedRegistry
├── models/
│ └── instrument.py
│ └── Instrument
├── validation/
│ ├── schema.py
│ └── values.py
├── handlers/
│ └── instrument_handler.py
│ └── DzengiInstrumentDocumentHandler
├── feeds/
│ └── instrument_feed.py
│ └── InstrumentFeed
└── adapters/
└── dzengi/
├── models.py
├── parser.py
├── mapper.py
└── rest.py
├── _PayloadRestClient
└── DzengiInstrumentDocumentSource
```
На текущем этапе ещё не реализован:
```text
service.py
```
---
## 33. Полная архитектурная цепочка после Build 011
После Build 011 существуют две связанные архитектурные части.
### Регистрация capability
```text
DzengiInstrumentDocumentSource
+
DzengiInstrumentDocumentHandler
InstrumentFeed
InstrumentFeedRegistry.register(
"dzengi",
feed,
)
```
### Получение capability будущим сервисом
```text
Acquisition Service
source_name = "dzengi"
InstrumentFeedRegistry.get("dzengi")
InstrumentFeedProtocol
```
После получения Feed будущий `Acquisition Service` сможет выполнить:
```text
InstrumentFeedProtocol.load_instruments()
tuple[Instrument, ...]
```
Эта orchestration logic относится к следующему:
```text
Build 012 — Acquisition Service
```
---
## 34. Полный acquisition pipeline после Build 011
Технически уже реализована следующая цепочка:
```text
source_name
InstrumentFeedRegistry
InstrumentFeedProtocol
InstrumentFeed
InstrumentDocumentSource
DzengiInstrumentDocumentSource
ExchangeRestClient.get_payload()
Dzengi REST API
```
После получения документа:
```text
object
InstrumentDocumentHandler
DzengiInstrumentDocumentHandler
validate_exchange_info_schema()
ValidatedExchangeInfoDocument
parse_exchange_info()
DzengiExchangeInfoResponse
validate_exchange_info_values()
map_dzengi_exchange_info_to_instruments()
tuple[Instrument, ...]
```
Однако Registry сам эту цепочку не запускает.
---
## 35. Что ещё не реализовано
После Build 011 отсутствуют:
```text
Acquisition Service;
официальная production composition;
equivalence verification с legacy implementation;
compatibility mapper Instrument → ExchangeSymbol;
переключение get_exchange_symbols();
перевод normalize_symbol()/symbol_candidates();
переключение validate_symbol();
переключение get_symbol_runtime_status();
перенос кэша.
```
Поэтому новая acquisition pipeline всё ещё остаётся изолированной от существующего production runtime.
---
## 36. Влияние на legacy-систему
Build 011 не подключён к существующим компонентам:
```text
ExchangeService
ExchangeSymbol
SymbolValidationResult
Telegram UI
AutoTrade
Market Stream
Market Data Runner
Execution Quality
Trading runtime
```
Не изменены:
```text
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
normalize_symbol()
symbol_candidates()
```
Старый production-путь продолжает работать без изменений.
---
## 37. Обратная совместимость
Подтверждено сохранение:
```text
сигнатур существующих legacy-методов;
старых импортов;
существующего формата legacy-ошибок;
Telegram UI;
автоторговли;
runtime-поведения;
legacy ExchangeSymbol;
legacy-кэша.
```
Build 011 имеет полную обратную совместимость.
---
## 38. Классификация изменений
| Изменение | Классификация |
|---|---|
| `InstrumentFeedRegistry` | Обязательное архитектурное изменение |
| Индексация Feed по `source_name` | Обязательное архитектурное изменение |
| Проверка `InstrumentFeedProtocol` | Улучшение надёжности |
| Запрет повторной регистрации | Улучшение надёжности |
| `InstrumentFeedRegistryError` | Обязательная диагностическая граница |
| Удаление внешних пробелов из имени | Улучшение надёжности |
| Сохранение case-sensitive ключей | Отсутствие скрытого изменения поведения |
| Хранение `Instrument` | Не выполняется |
| Вызов Feed | Не выполняется |
| Кэширование | Не выполняется |
| Изменение legacy-кода | Отсутствует |
| Изменение production-поведения | Отсутствует |
---
## 39. Условие завершения Build 011
Все условия выполнены:
```text
InstrumentFeedRegistry
реализован;
InstrumentFeedProtocol
может быть зарегистрирован;
Feed
возвращается по source_name;
identity Feed
сохраняется;
несколько источников
поддерживаются;
внешние пробелы имени
удаляются;
регистр имени
сохраняется;
пустые ключи
отклоняются;
повторная регистрация
отклоняется;
неуспешная повторная регистрация
не заменяет исходный Feed;
отсутствующий Feed
вызывает InstrumentFeedRegistryError;
невалидный Feed
отклоняется;
Registry
не вызывает load_instruments();
Registry
не хранит Instrument;
Registry
не выполняет кэширование;
unit-тесты
проходят;
полный pytest
проходит;
production runtime
не затронут.
```
---
## 40. Итог Build 011
Build 011 завершён успешно.
Реализовано:
```text
InstrumentFeedRegistry
source_name
register()
InstrumentFeedProtocol
```
и:
```text
source_name
get()
тот же InstrumentFeedProtocol
```
Подтверждено:
```text
25 Registry tests passed
167 total project tests passed
Python compilation passed
No premature production integration detected
Legacy bot behavior unchanged
```
Итоговый статус:
```text
BUILD 011 — COMPLETE
```
---
## 41. Следующий этап
Следующий этап утверждённого плана:
```text
Build 012 — Acquisition Service
```
Его задача — реализовать application-level orchestration над Registry и Feed:
```text
source_name
Acquisition Service
InstrumentFeedRegistry
InstrumentFeedProtocol
load_instruments()
tuple[Instrument, ...]
```
Build 012 не должен:
```text
изменять ExchangeService;
переключать legacy get_exchange_symbols();
создавать compatibility mapper;
переносить legacy-кэш;
подключаться к Telegram UI;
изменять AutoTrade;
изменять trading runtime.
```
После успешного завершения Build 012 будет закончена изолированная новая acquisition pipeline, после чего можно будет перейти к:
```text
Build 013 — проверка эквивалентности старой и новой реализации.
```