Build 060.27: implement Persistent Market Data Storage

This commit is contained in:
2026-08-01 03:22:25 +03:00
parent cb8acfe5fe
commit 58e5a12a4d
54 changed files with 10243 additions and 101 deletions

View File

@@ -0,0 +1,224 @@
# Build 060.27 — Persistent Market Data Storage
**Engineering Migration Report**
---
## Контроль документа
| Свойство | Значение |
|---|---|
| Build | 060.27 |
| Статус | Completed |
| Подсистема | Market Data Acquisition / Persistent Storage |
| Компонент | Canonical Market Data Storage |
| Дата завершения | 2026-08-01 |
| Версия | 1.0 |
---
## Связанные документы
- `build_060_27_architecture.md` — архитектура, решения и подробные
результаты подэтапов 060.27.0060.27.8;
- `build_060_26.md` — итог Runtime Integration and Regression;
- `dzentra_target_architecture.md` — место Market Data Storage в целевой
архитектуре Dzentra;
- `master-roadmap.md` — дальнейшая последовательность Build.
---
## 1. Назначение Build
Build 060.27 добавил долговременное PostgreSQL-хранилище Canonical
Market Data. Production Trade Stream теперь может сохранять Trades
независимо от времени жизни процесса, а Quotes и Candles получили
готовые storage-контракты и repositories без подключения существующих
consumers.
Главная гарантия интеграции:
```text
validated Canonical Trade
durable PostgreSQL write
operational checkpoint advancement
```
Ошибка включённого persistent storage является фатальной. Runtime не
продвигает checkpoint и не продолжает работу так, будто Trade сохранён.
---
## 2. Завершённые подэтапы
| Подэтап | Название | Статус |
|---|---|---|
| 060.27.0 | Storage Contract and Boundaries | Accepted |
| 060.27.1 | PostgreSQL Schema, Migrations and Pool | Accepted |
| 060.27.2 | Persistent Trade Repository | Accepted |
| 060.27.3 | Quote and Candle Repositories | Accepted |
| 060.27.4 | Storage API and Retention | Accepted |
| 060.27.5 | Trade Runtime Persistence Integration | Accepted |
| 060.27.6 | Bootstrap and Lifecycle Integration | Accepted |
| 060.27.7 | Integration and Failure Verification | Accepted |
| 060.27.8 | Final Regression and Acceptance | Accepted |
Каждый подэтап проходил отдельный read-only review. Findings
исправлялись и закрывались regression-тестами до принятия.
---
## 3. Реализованная архитектура
### 3.1. Schema, migrations и pool
Создана PostgreSQL schema `market_data` с partitioned таблицами:
- `trades` — Canonical Trades;
- `quotes` — неизменяемые Canonical Quote snapshots;
- `candle_revisions` — наблюдавшиеся ревизии Candles;
- `partition_registry` — реестр Dzentra-managed partitions.
Семь упорядоченных миграций применяются в одной транзакции под
transaction-level advisory lock. История хранится в
`public.storage_schema_migrations`; неизвестная версия или другое имя
миграции являются фатальным schema mismatch.
`PostgresConnectionPool` не выполняет I/O в конструкторе. Pool явно
открывается Application lifecycle перед Runtime и закрывается только
после полной остановки Trade Runtime.
### 3.2. Canonical repositories
Repository-контракты различают три результата:
- `INSERTED` — сохранено новое событие;
- `DUPLICATE` — идентичное событие уже существует;
- `PROVENANCE_UPDATED` — Canonical payload совпал, добавлен новый путь
наблюдения.
Trade identity задаётся `venue + symbol + trade_id + executed_at`.
Время защищает историю от коллизии после полного signed 32-bit rollover
Trade ID. Разный transport source не создаёт Canonical conflict, а
различие цены, количества, времени или стороны создаёт явную ошибку.
Quote identity использует `venue + symbol + received_at`. Candle
identity использует `venue + symbol + interval + open_time +
observed_at`, поэтому промежуточные и финальные ревизии не
перезаписываются скрытно.
Одиночные и пакетные операции транзакционны. Конфликт или database
error откатывает всю пакетную запись.
### 3.3. Partitions и retention
Каждая основная таблица имеет default partition. Управляемый partition
manager под общим advisory lock:
1. проверяет фактическое состояние PostgreSQL и registry;
2. создаёт месячную таблицу;
3. переносит подходящие default rows;
4. подключает partition в той же транзакции.
Retention по умолчанию выключен. Явный запуск удаляет только
зарегистрированные истёкшие partitions и строки раньше точного UTC
cutoff. Очистка всех выбранных типов выполняется атомарно.
### 3.4. Runtime и Bootstrap
Live и Recovery используют один `TradeObservationSinkProtocol` через
общий `TradeStreamConsistencyController`. Единственный владелец записи
обеспечивает одинаковую последовательность для обоих путей данных.
Синхронная PostgreSQL-запись выполняется вне asyncio event loop через
принадлежащую Runtime задачу. Cancellation ожидает уже начатую запись и
не оставляет фонового worker.
Storage включается только явным `MARKET_DATA_STORAGE_ENABLED=true` и
требует включённый Trade Stream. При выключенном флаге зависимости не
строятся, pool не открывается, migrations не запускаются.
---
## 4. Real PostgreSQL verification
Безопасный opt-in harness требует одновременно:
- `DZENTRA_RUN_POSTGRES_TESTS=1`;
- отдельный `DZENTRA_TEST_POSTGRES_DSN`;
- явный localhost, loopback address или local socket;
- имя базы с префиксом `dzentra_test_`.
PostgreSQL service, неявный endpoint и смешанный local/remote fallback
запрещены. Перед destructive cleanup harness повторно проверяет точное
имя фактической базы и специальное имя контрольного соединения.
Настоящий PostgreSQL подтвердил:
- атомарность migrations и repositories;
- участие обеих concurrent migration/partition caller-сессий;
- insert, duplicate, provenance и conflict paths;
- перенос default rows и точность retention cutoff;
- Runtime persistence через Live и reconnect/recovery;
- fatal propagation database failure;
- правильное закрытие pool и отсутствие оставшихся соединений.
---
## 5. Финальные результаты
Итоговая приёмка выполнена 2026-08-01:
```text
Targeted Storage/Runtime unit suite: 307 passed
PostgreSQL control run: 15 passed
PostgreSQL repeated ten times: 150 passed
Combined integration suite: 26 passed
Fixed stress target: 3 passed, 1 deselected
PostgreSQL suite without opt-in: 15 skipped
Full offline regression: 2119 passed, 31 deselected
git diff --check: clean
Untracked files diff check: clean
```
В integration и stress наборах `ResourceWarning` считался ошибкой.
Одноразовый PostgreSQL 16 работал на `tmpfs` и после приёмки был
остановлен и удалён. Итоговый read-only review не обнаружил открытых
findings.
---
## 6. Границы Build
Build 060.27 намеренно не реализует:
- persistent Runtime checkpoint;
- Startup Recovery после перезапуска процесса;
- Historical Queries и Data Access Layer;
- Replay API и Analytics API;
- автоматический partition/retention scheduler;
- подключение существующих Quote/Candle consumers;
- хранение raw exchange documents.
Persistent Checkpoint и Startup Recovery относятся к Build 060.28.
Historical Access и Replay относятся к Build 060.29. Полная итоговая
ревизия документации подсистемы остаётся задачей Build 060.30.
Постороннее пользовательское изменение `.gitignore` не относится к
Build 060.27 и не должно включаться в его staging.
---
## 7. Итог
Build 060.27 завершён и принят.
Dzentra получила транзакционное долговременное хранилище Canonical
Market Data и production persistence для Trades. Operational checkpoint
теперь продвигается только после подтверждённой durable-записи, а
ошибка Storage завершает Runtime без скрытой потери истории.
Следующий этап — Build 060.28, Persistent Checkpoint and Startup
Recovery.

View File

@@ -0,0 +1,509 @@
# Build 060.27 — Persistent Market Data Storage Architecture
**Статус:** Completed
**Build:** 060.27
**Подсистема:** Market Data Acquisition / Persistent Storage
**Дата начала:** 2026-07-31
**Дата завершения:** 2026-08-01
**Версия документа:** 1.5
---
## 1. Назначение
Build 060.27 создаёт долговременное PostgreSQL-хранилище Canonical
Market Data. Данные должны сохраняться независимо от времени жизни
процесса и стать основой для Persistent Checkpoint, исторических
запросов и Replay в следующих Build.
В Build реализуется production-интеграция только для Trades. Для Quotes
и Candles создаются схема, repository-контракты и Storage API, но
существующие legacy/runtime consumers пока к ним не подключаются.
---
## 2. Статус подэтапов
| Подэтап | Название | Статус |
|---|---|---|
| 060.27.0 | Storage Contract and Boundaries | Accepted |
| 060.27.1 | PostgreSQL Schema, Migrations and Pool | Accepted |
| 060.27.2 | Persistent Trade Repository | Accepted |
| 060.27.3 | Quote and Candle Repositories | Accepted |
| 060.27.4 | Storage API and Retention | Accepted |
| 060.27.5 | Trade Runtime Persistence Integration | Accepted |
| 060.27.6 | Bootstrap and Lifecycle Integration | Accepted |
| 060.27.7 | Integration and Failure Verification | Accepted |
| 060.27.8 | Final Regression and Acceptance | Accepted |
060.27.0060.27.1 прошли отдельный read-only review. После исправления
cleanup при системном прерывании и удаления двух дублирующих индексов
повторный review завершён без findings.
060.27.2 прошёл отдельный read-only review конкурентных вставок,
Canonical conflict, provenance, batch atomicity и error propagation.
Review завершён без findings.
060.27.3 прошёл отдельный read-only review Quote timestamp collision,
Candle revision identity, interval case, concurrent provenance и
transaction rollback. Review завершён без findings.
060.27.4 принят после исправления проверки фактического PostgreSQL
partition bound и изоляции Retention от concurrent writers. Повторный
read-only review concurrent creation, default-row move, DDL safety,
точности cutoff и полного rollback завершён без findings.
060.27.5 принят после отдельного read-only review durable-before-
checkpoint ordering, duplicate provenance, fatal error propagation,
cancellation-safe live offload и единственного владельца записи.
Review завершён без findings; целевые тесты и полная регрессия прошли.
060.27.6 реализует disabled-by-default Storage feature flag, общий
bootstrap graph и управляемый Application lifecycle. Целевые тесты и
полная регрессия прошли. Отдельный read-only review feature flag,
отсутствия Storage I/O при composition, partial startup, shutdown order,
repeated cancellation и task ownership завершён без findings.
060.27.7 добавляет безопасный opt-in PostgreSQL harness и выполняет
реальные schema, repository, partition, retention, Runtime persistence и
failure-сценарии на одноразовой локальной базе. Настоящий PostgreSQL
выявил ошибку параметризации partition DDL; границы CHECK и ATTACH теперь
встраиваются как безопасные `psycopg.sql.Literal`.
Первичный review выявил обход local-only проверки через PostgreSQL
service/неявный endpoint и недостаточное доказательство одновременного
участия concurrent callers. Harness теперь запрещает service, неявные и
смешанные local/remote endpoints, а непосредственно перед destructive
cleanup повторно сверяет точное имя базы и identity контрольного
соединения. Migration и partition tests удерживают advisory lock, пока
PostgreSQL не подтвердит ожидание обеих caller-сессий; Trade writers
синхронизируются общим барьером. Повторный review завершён без findings,
подэтап принят.
060.27.8 выполнил финальную verification-only приёмку без изменения
production-кода. Целевой unit-набор, новый одноразовый PostgreSQL,
десять повторов PostgreSQL integration, общий integration, fixed stress
и полная offline-регрессия прошли. Итоговый read-only review не выявил
новых findings; подэтап и Build 060.27 приняты.
---
## 3. Принятые архитектурные решения
1. В production runtime этого Build подключается только хранение Trades.
2. Для Quotes и Candles создаётся storage foundation без изменения
существующего legacy/runtime wiring.
3. Operational checkpoint можно продвигать только после успешного
сохранения соответствующего Trade.
4. Ошибка включённого persistent storage является фатальной: Runtime не
должен скрывать потерю истории и продолжать работу как будто всё
успешно.
5. Raw exchange documents в Build 060.27 не сохраняются. Хранилище
принимает только прошедшие validation и mapping Canonical Models.
6. Canonical history по умолчанию не удаляется. Retention включается
только явной настройкой и отдельной операцией.
7. Идентичность Trade задаётся кортежем `venue`, `symbol`, `trade_id`,
`executed_at`. Включение времени защищает от коллизии после полного
цикла signed 32-bit Trade ID.
8. Market Data размещаются в PostgreSQL schema `market_data`, изменения
схемы выполняются упорядоченными SQL-миграциями, соединения выдаёт
один явно управляемый pool.
---
## 4. Архитектурная граница
```text
Canonical Trade / Quote / Candle
market_data.storage contracts
PostgreSQL repositories (060.27.2060.27.3)
storage.PostgresConnectionPool
PostgreSQL schema market_data
```
Storage зависит от Canonical Models. Canonical Models, Consistency,
Recovery и Transport не зависят от PostgreSQL или `psycopg`.
Trade Runtime использует общий acquisition-side контракт
`TradeObservationSinkProtocol`. Его Storage adapter знает о
`TradeStorageProtocol`, venue и UTC clock, но Consistency и Runtime не
знают о PostgreSQL repository или pool:
```text
Live / Recovery
shared TradeStreamConsistencyController
│ validated observation
TradeObservationSinkProtocol
TradeStorageObservationSink → TradeStorageProtocol
```
Контракты возвращают явный результат записи:
- `INSERTED` — новое событие сохранено;
- `DUPLICATE` — полностью совпадающее событие уже существовало;
- `PROVENANCE_UPDATED` — Canonical payload совпал, но обновлена
информация о наблюдении события.
Несовпадающий payload под тем же ключом не является дубликатом и должен
приводить к `MarketDataStorageConflictError`.
---
## 5. Модель хранения
### 5.1. Trades
Таблица `market_data.trades` хранит Canonical Trade и технические поля
provenance:
```text
identity = venue + symbol + trade_id + executed_at
provenance = source + observation_sources
+ first_observed_at + last_observed_at
```
`source` сохраняет первый зафиксированный путь получения Trade.
`observation_sources` накапливает уникальные Canonical source names,
например WebSocket и REST Recovery, не превращая различие transport
provenance в конфликт биржевого факта.
`trade_id` ограничен signed 32-bit диапазоном. Это соответствует
единому rollover-aware контракту Build 060.26, но база не пытается
сортировать события только по числовому ID. Исторический порядок
определяется прежде всего `executed_at`.
### 5.2. Quotes
Таблица `market_data.quotes` хранит неизменяемые Canonical snapshots.
Ключ снимка: `venue`, `symbol`, `received_at`. Nullable
`exchange_timestamp` сохраняется как часть payload, но не используется
как обязательный ключ.
`source` сохраняет первый зафиксированный путь получения, а
`observation_sources` — уникальные источники того же snapshot. Два
разных payload с одним `received_at` являются явным timestamp conflict;
repository не изменяет полученное время и не создаёт скрытый surrogate.
### 5.3. Candles
Свеча может меняться до закрытия интервала. Поэтому
`market_data.candle_revisions` не перезаписывает строку на месте, а
хранит последовательность наблюдавшихся ревизий. Ключ:
```text
venue + symbol + interval + open_time + observed_at
```
Флаг `is_final` отделяет промежуточную ревизию от финальной.
Изменение содержимого или `is_final` сохраняется как новая ревизия с
новым `observed_at`. Разный source той же ревизии обновляет только
`observation_sources`. Регистр `interval` сохраняется, потому что имена
рыночных интервалов могут быть чувствительны к регистру.
### 5.4. Партиционирование и retention
Все три таблицы объявлены как range-partitioned по времени события и
имеют default partition. Это даёт безопасную запись до появления
автоматического partition manager.
Default partition остаётся страховкой для записи до создания нужного
месяца. `PostgresMarketDataPartitionManager` под transaction-level
advisory lock создаёт отдельную месячную таблицу, блокирует default
partition, переносит подходящие строки и подключает таблицу как
partition в одной транзакции.
Созданные Dzentra partitions регистрируются в
`market_data.partition_registry`. Существующая незарегистрированная
таблица, отсутствующая зарегистрированная таблица или detached
partition считаются ошибкой конфигурации и не принимаются молча.
Canonical retention по умолчанию не ограничен.
`PostgresMarketDataRetentionService` вообще не обращается к базе при
выключенной policy. Включённый явный запуск удаляет целиком только
зарегистрированные истёкшие partitions, затем удаляет строки до точного
UTC cutoff из оставшихся/default partitions. Все настроенные типы
очищаются в одной транзакции.
---
## 6. Версионированные миграции
`StorageMigrationRunner`:
1. получает соединение через переданный provider;
2. берёт PostgreSQL transaction-level advisory lock;
3. создаёт таблицу истории `public.storage_schema_migrations`;
4. сверяет уже применённые версии и их неизменяемые имена;
5. применяет отсутствующие версии строго по порядку;
6. записывает версию в историю в той же транзакции.
Неизвестная версия в базе или другое имя уже применённой версии
считается ошибкой. Миграции не должны молча принимать расхождение
между кодом и фактической схемой.
Начальный набор:
| Версия | Назначение |
|---|---|
| 1 | schema `market_data` |
| 2 | partitioned Canonical Trades |
| 3 | partitioned Canonical Quotes |
| 4 | partitioned immutable Candle revisions |
| 5 | multiple Trade observation sources |
| 6 | Quote and Candle observation sources |
| 7 | managed monthly partition registry |
---
## 7. Connection Pool lifecycle
`PostgresConnectionPool` имеет явный lifecycle:
```text
construct → open → borrow/return connections → close
```
Конструктор не открывает сеть. `open()` ждёт готовности минимального
числа соединений и при ошибке освобождает частично созданный pool.
`connection()` до успешного `open()` запрещён. `close()` идемпотентен.
В 060.27.6 pool, migration runner, Trade repository и observation sink
собираются без сетевых операций. Application запускает blocking startup
через owned `asyncio.to_thread` task до Telegram и Trade Runtime:
```text
pool.open → migrations.run → Telegram + Trade Runtime
```
Shutdown выполняется в обратном порядке зависимостей. Сначала полностью
останавливается Trade Runtime и завершаются его Storage-вызовы, затем
закрывается pool и после этого bot session.
---
## 8. Обработка ошибок
- ошибки lifecycle pool оборачиваются в
`PostgresConnectionPoolError`;
- ошибки запуска или расхождения миграций оборачиваются в
`StorageMigrationError`;
- repository validation, conflict и database operations используют
иерархию `MarketDataStorageError`;
- `KeyboardInterrupt` и `SystemExit` не превращаются в storage errors;
- ошибка включённого persistent storage фатальна и не скрывается;
- ошибка cleanup не заменяет исходную startup/runtime ошибку;
- cancellation ждёт уже начатый blocking lifecycle operation.
---
## 9. Границы реализации 060.27.0060.27.8
Реализовано:
- отдельный пакет `market_data.storage`;
- write-only Protocol contracts для Trades, Quotes и Candles;
- результаты одиночной и пакетной идемпотентной записи;
- отдельная иерархия ошибок Market Data Storage;
- versioned migration runner с advisory lock;
- partitioned PostgreSQL schema для трёх Canonical типов;
- явно открываемый и закрываемый PostgreSQL connection pool;
- синхронный `PostgresTradeRepository` без ownership pool lifecycle;
- одиночная и атомарная пакетная запись Canonical Trades;
- exact duplicate, provenance update и canonical conflict semantics;
- стабильный порядок batch-записи и rollback всей пачки при ошибке;
- синхронные `PostgresQuoteRepository` и `PostgresCandleRepository`;
- immutable Quote snapshot и Candle revision conflict semantics;
- multiple-source provenance для Trades, Quotes и Candle revisions;
- общие validation helpers трёх PostgreSQL repositories;
- единый write-only `MarketDataStorage` facade без lifecycle ownership;
- UTC monthly partition descriptors для трёх Canonical типов;
- транзакционное создание, перенос и attach managed partitions;
- реестр Dzentra-managed partitions в migration 7;
- disabled-by-default retention с отдельным явным запуском;
- удаление полных partitions и точная очистка partial/default данных;
- общий advisory lock для partition lifecycle и retention;
- закреплённые версии `psycopg` и `psycopg-pool`;
- общий optional `TradeObservationSinkProtocol` для Live и Recovery;
- единственный persistence owner в `TradeStreamConsistencyController`;
- durable write до продвижения operational checkpoint;
- запись корректных дубликатов для обновления provenance;
- выполнение синхронной live-записи вне asyncio event loop;
- cancellation-safe ожидание уже начатой live-записи при shutdown;
- отдельный `MarketDataStorageSettings` и безопасный feature flag;
- единый bootstrap graph pool → migrations → Trade repository → sink;
- использование одного Settings snapshot и существующих `DB_*`;
- Storage startup до Telegram и Trade Runtime;
- partial startup cleanup при migration failure;
- закрытие pool только после полной остановки Trade Runtime;
- cancellation-safe Application startup и shutdown tasks.
- opt-in harness только для явно указанной локальной disposable базы;
- отсутствие fallback PostgreSQL integration-тестов на application
`DB_*`;
- real PostgreSQL verification миграций, repositories и транзакций;
- real concurrent migration, Trade write и partition callers;
- real partition move, retention cutoff и multi-type rollback;
- local WebSocket/REST Runtime persistence в настоящий PostgreSQL;
- real reconnect/recovery provenance через общий Trade repository;
- fatal persistence failure без ложного продвижения checkpoint;
- проверка Application ownership pool и отсутствия оставшихся соединений.
Не реализовано на этом шаге:
- чтение истории;
- автоматический partition/retention scheduling;
- подключение legacy Quote/Candle consumers.
---
## 10. Критерии приёмки 060.27.0060.27.8
1. Storage contracts не импортируют Runtime, Consistency или Recovery.
2. Конструктор pool не выполняет сетевых операций.
3. Частично открытый pool очищается при ошибке startup.
4. Повторный `close()` безопасен.
5. Миграции выполняются один раз, последовательно и атомарно.
6. Concurrent migration runners сериализуются advisory lock.
7. Schema содержит явные idempotency keys и Canonical constraints.
8. Trade Runtime не импортирует PostgreSQL repository или pool.
9. Целевые тесты и обычная регрессия проходят успешно.
10. Источник доставки не является частью Canonical Trade conflict.
11. Один конфликт или database error откатывает весь Trade batch.
12. Repository не владеет pool lifecycle и не импортирует Runtime.
13. Quote timestamp collision и Candle revision conflict не скрываются.
14. Quote/Candle source обновляет provenance, а не Canonical payload.
15. Существующие Quote/Candle consumers не подключены к PostgreSQL.
16. Storage facade делегирует записи и не владеет pool lifecycle.
17. Имена таблиц и колонок выбираются только из закрытого набора и
экранируются через `psycopg.sql.Identifier`.
18. Повторное создание месяца идемпотентно, а расхождение реестра и
PostgreSQL является явной ошибкой.
19. Неудачный setup partition откатывает создание, перенос и registry.
20. Retention требует отдельного `enabled` и положительного окна.
21. Выключенный retention не получает соединение и ничего не удаляет.
22. Ошибка очистки одного типа откатывает всю retention-операцию.
23. Runtime, Scheduler и существующие consumers не импортируют новый
Storage API.
24. Live и Recovery используют один persistence sink через общий
`TradeStreamConsistencyController`.
25. Validated Trade сохраняется до изменения checkpoint и dedup window.
26. Корректный дубликат сохраняется для provenance без продвижения
checkpoint.
27. Ordering error, conflicting duplicate и invalid Trade не передаются
в Storage.
28. Ошибка Storage не скрывается и не продвигает checkpoint.
29. Live persistence выполняется через owned `asyncio.to_thread` task
при закрытом live-processing gate.
30. Shutdown ждёт уже начатую live-запись и не оставляет её фоновой.
31. Отсутствующий optional sink сохраняет прежнее поведение Composition.
32. Bootstrap/settings не изменены, PostgreSQL pool не открывается.
33. Целевой набор и полная обычная регрессия проходят успешно.
34. Storage feature flag выключен по умолчанию и не строит зависимости.
35. Включённый Storage требует включённый Trade Stream.
36. Pool settings валидируются только при включённом Storage.
37. Bootstrap graph не открывает PostgreSQL и не запускает migrations.
38. Migration runner и Trade repository используют один pool identity.
39. Один observation sink передаётся общим Live/Recovery controller.
40. Pool открывается и migrations завершаются до root Runtime tasks.
41. Migration failure закрывает открытый pool и не запускает Runtime.
42. Blocking startup/shutdown выполняются вне asyncio event loop.
43. Cancellation ждёт завершения уже начатой blocking операции.
44. Trade Runtime полностью остановлен до закрытия pool.
45. Storage cleanup error не заменяет исходную Runtime ошибку.
46. Retention и partition scheduling автоматически не запускаются.
47. PostgreSQL integration требует точный opt-in flag и отдельный DSN.
48. Integration DSN принимает только localhost/local socket и database
name с префиксом `dzentra_test_`.
49. Harness не читает application `DB_*` и не управляет Docker скрытно.
50. Migrations 17 реально применяются в PostgreSQL и повторный запуск
ничего не меняет.
51. Два real migration runners применяют каждую версию ровно один раз.
52. Trades, Quotes и Candle revisions проходят real insert, duplicate,
provenance и conflict paths.
53. Trade batch реально откатывает предшествующую вставку при конфликте.
54. Два concurrent Trade writers сходятся к одной Canonical строке.
55. Partition DDL использует безопасные SQL literals вместо запрещённых
PostgreSQL parameters в `ALTER TABLE`.
56. Real partition manager переносит default rows и сериализует callers.
57. Real retention соблюдает точный cutoff и общий transaction rollback.
58. Live и reconnect/recovery Runtime сохраняют Trades в один repository.
59. Закрытый pool приводит к fatal Runtime error без продвижения
checkpoint и без записи проблемного Trade.
60. Application открывает и мигрирует Storage до Runtime, а закрывает pool
после Runtime; после shutdown нет owned tasks и pool connections.
61. Выключенный opt-in PostgreSQL-набор не требует локальной базы и
корректно пропускается обычной regression-командой.
62. Destructive cleanup непосредственно перед DDL повторно проверяет
точное имя disposable базы и специальный control application name.
63. Concurrent migration и partition tests подтверждают в PostgreSQL,
что обе caller-сессии действительно ожидают один advisory lock.
### 10.1. Opt-in PostgreSQL verification
Тесты никогда не используют production `DB_*`. Первый запуск требует
явного одноразового PostgreSQL и выполняется командой вида:
```bash
DZENTRA_RUN_POSTGRES_TESTS=1 \
DZENTRA_TEST_POSTGRES_DSN=postgresql://test:test@127.0.0.1:55432/dzentra_test_060_27 \
.venv/bin/python -m pytest -q -m integration \
tests/integration/market_data/storage
```
Harness проверяет имя базы и каждый local endpoint до соединения,
запрещает service/implicit fallback, берёт session advisory lock и
очищает только `market_data` и собственную таблицу migration history.
Перед каждым destructive cleanup он повторно подтверждает фактическое
имя disposable базы и identity контрольного соединения.
---
## 11. Финальная приёмка 060.27.8
Финальный этап выполнялся по verification-only правилу: production-код
не изменялся, потому что проверки не выявили нового дефекта.
Результаты на 2026-08-01:
```text
Targeted Storage/Runtime unit suite: 307 passed
PostgreSQL control run: 15 passed
PostgreSQL repeated ten times: 150 passed
Combined integration suite: 26 passed
Fixed stress: 3 passed, 1 deselected
PostgreSQL suite without opt-in: 15 skipped
Full offline regression: 2119 passed, 31 deselected
git diff --check: clean
Untracked files diff check: clean
```
Во всех integration и stress проверках `ResourceWarning` считался
ошибкой. Одноразовый PostgreSQL 16 использовал временное `tmpfs`
хранилище и после проверки был остановлен и удалён.
Итоговый read-only review повторно проверил архитектурные зависимости,
SQL/DDL safety, durable-before-checkpoint ordering, fatal persistence
failure, lifecycle pool и Runtime, opt-in isolation и test coverage.
Новых findings не обнаружено.
## 12. Следующий Build
Build 060.27 завершён и принят. Следующий этап — Build 060.28,
Persistent Checkpoint and Startup Recovery.

View File

@@ -6,10 +6,10 @@
|---|---|
| Тип | Master Delivery Roadmap |
| Статус | Active |
| Версия | 2.0 |
| Дата актуализации | 2026-07-31 |
| Текущий завершённый Build | 060.26 |
| Следующий Build | 060.27 |
| Версия | 2.1 |
| Дата актуализации | 2026-08-01 |
| Текущий завершённый Build | 060.27 |
| Текущий Build | 060.28 — Planned |
---
@@ -42,27 +42,26 @@ test evidence находятся в документах конкретных Bu
```text
Market Data Acquisition
Trades Feed / Time & Sales
Persistent Market Data Storage завершён
Build 060.26 — Integration and Regression
Build 060.27
Completed
Completed → следующий Build 060.28
```
060.26.0060.26.6 приняты. Trade Stream:
Build 060.27 завершён и принят:
- проверен через настоящие локальные WebSocket и HTTP соединения;
- прошёл reconnect, recovery, fault и cancellation scenarios;
- прошёл fixed stress, 120-секундный и 900-секундный soak;
- проверен через opt-in Dzengi live scenario;
- использует единый rollover-aware контракт signed 32-bit Trade ID;
- завершил полную offline-регрессию из 1930 тестов.
- реализованы versioned PostgreSQL migrations и управляемый pool;
- созданы Canonical repositories для Trades, Quotes и Candles;
- реализованы monthly partitions, provenance и opt-in retention;
- Trade Runtime сохраняет Live и Recovery Trades до checkpoint;
- Bootstrap управляет порядком Storage startup и shutdown;
- real PostgreSQL integration и финальная регрессия приняты.
Подробности:
```text
docs/migrations/build_060_26_architecture.md
docs/migrations/build_060_26.md
docs/migrations/build_060_27_architecture.md
```
---
@@ -82,6 +81,7 @@ docs/migrations/build_060_26.md
| 060.24 | Runtime Recovery Architecture | Completed |
| 060.25 | Production Runtime Integration | Completed |
| 060.26 | Integration and Regression | Completed |
| 060.27 | Persistent Market Data Storage | Completed |
## Build 060.26 — Integration and Regression
@@ -100,11 +100,11 @@ docs/migrations/build_060_26.md
Build не должен превращать сетевые сценарии в обязательную часть
обычного unit suite.
## Следующие Build
## Текущий и следующие Build
### Build 060.27 — Persistent Market Data Storage
**Статус:** Planned
**Статус:** Completed
Назначение:
@@ -118,6 +118,8 @@ Build не должен превращать сетевые сценарии в
Результат: история рынка сохраняется независимо от торгового цикла и
времени жизни процесса.
Подробный итог: `docs/migrations/build_060_27.md`.
### Build 060.28 — Persistent Checkpoint and Startup Recovery
**Статус:** Planned
@@ -1730,6 +1732,6 @@ read-only архитектурного анализа. Старый ориент
Актуальная контрольная точка:
```text
Завершён: Build 060.26Integration and Regression
Следующий: Build 060.27 — Persistent Market Data Storage
Завершён: Build 060.27Persistent Market Data Storage
Следующий: Build 060.28 — Persistent Checkpoint and Startup Recovery
```