Files
dzentra_bot/docs/migrations/build_060_29_architecture.md

74 KiB
Raw Permalink Blame History

Build 060.29 — Market Data Access and Replay Architecture

Статус: Accepted

Build: 060.29

Подсистема: Market Data / Historical Access and Replay

Дата начала: 2026-08-02

Дата завершения: 2026-08-02

Версия документа: 1.9


1. Назначение

Build 060.29 добавляет безопасное чтение сохранённых Canonical Market Data и их детерминированное воспроизведение.

Build опирается на:

  • PostgreSQL Canonical Storage из 060.27;
  • Persistent Checkpoint и Startup Recovery из 060.28;
  • существующие неизменяемые Canonical Trade, Quote и Candle.

Итоговая цепочка должна выглядеть так:

PostgreSQL Canonical Market Data
        ↓
Historical Access
        ↓
bounded immutable ReplayPlan
        ↓
ReplaySession + Virtual Clock
        ↓
Canonical consumer

Historical Access и Replay являются read-side подсистемами. Они не заменяют Acquisition Runtime, не продвигают operational checkpoint и не записывают повторно воспроизводимые события в Market Data Storage.


2. Статус подэтапов

Подэтап Название Статус
060.29.0 Architecture, Boundaries and Ordering Policy Accepted
060.29.1 Historical Access and Replay Contracts Accepted
060.29.2 Global Replay Sequence Migration and PostgreSQL Trade History Accepted
060.29.3 Quote/Candle History and Snapshot Plan Builder Accepted
060.29.4 Deterministic Replay Clock Accepted
060.29.5 Replay Session and Engine Accepted
060.29.6 Consumer and Composition Integration Accepted
060.29.7 PostgreSQL Replay and Failure Verification Accepted
060.29.8 Final Regression and Acceptance Accepted

Подэтапы 060.29.0060.29.6 приняты после отдельных приёмочных read-only review. Реализация 060.29.2 ограничена versioned migration 9, совместимостью существующих writers и отдельным PostgreSQL Trade History reader. 060.29.3 добавляет Quote/Candle History и отдельный bounded Replay snapshot. 060.29.4 добавляет детерминированные UTC-часы без wall clock, reset и скрытого lifecycle. На этом этапе Build оставался In Progress. Контракт 060.29.5 утверждён; отдельный ReplayEngine не создаётся, а принятый playback engine реализован внутри ReplaySession.run(). Контракт 060.29.6 утверждён: composition использует обязательную фабрику consumer, один синхронный blocking ReplaySessionFactory, новый граф зависимостей на каждый вызов и не выполняет автоматический startup. Реализация 060.29.6 принята после чистого формального read-only review.

Контракт 060.29.7 утверждён как verification-only: новые тесты используют существующий безопасный opt-in PostgreSQL harness, реальные Storage, Historical Access, ReplayPlan Builder, Composition и Session, но не добавляют production consumer, Bootstrap wiring или automatic startup. Production-код заранее не изменяется.

060.29.7 принят после исправления найденных Pylance/Pyright diagnostics, введения обязательного static gate, полной повторной регрессии и чистого формального read-only review. Единственная production-правка подэтапа уточняет статическое сужение уже проверенного точного union-типа без изменения runtime-контракта или поведения Replay.

060.29.8 выполнил финальную verification-only матрицу без изменения production-кода. Повторены статическая проверка, unit, PostgreSQL, integration, opt-out, fixed stress и полная offline-регрессия. Три независимых read-only review не выявили открытых findings; подэтап и Build 060.29 приняты.


3. Исходные инварианты 060.27060.28

  1. Canonical Market Data уже прошли transport validation и mapping.
  2. MarketDataStorage остаётся фасадом только для записи.
  3. Checkpoint reads остаются узкой границей Startup Hydration и не превращаются в Historical API.
  4. Production Runtime сохраняет Canonical Trade до продвижения operational checkpoint.
  5. Live и Recovery используют одного владельца записи Trades.
  6. PostgreSQL pool имеет явный Application lifecycle.
  7. Consistency, Recovery и Runtime не зависят от psycopg.
  8. Trade ID использует signed 32-bit rollover-aware контракт.
  9. Retention по умолчанию выключен и запускается только явно.

4. Принятые архитектурные решения

4.1. Отдельная read-side граница

Historical Access размещается отдельно от существующего write-only Storage API:

market_data.access contracts
        ↑
PostgreSQL History adapters

Существующие TradeStorageProtocol, QuoteStorageProtocol, CandleStorageProtocol, TradeCheckpointStorageProtocol и MarketDataStorage не расширяются историческими запросами.

PostgreSQL implementation зависит от DB-neutral read-контрактов. Контракты не импортируют psycopg, pool, Bootstrap, Runtime, Recovery, Telegram или Trading.

4.2. Scope типов данных

Historical Access предусматривает чтение трёх уже существующих Canonical таблиц:

  • Trades;
  • Quotes;
  • Candle revisions.

Это не подключает persistent Quote/Candle consumers и не объявляет их Production Runtime завершённым. Полная production-проверка Live → Storage → Historical → Replay в этом Build обязательна для Trades. Quote/Candle read-path проверяется на repository и PostgreSQL уровне.

4.3. Совместимость Live и Replay

Live и Replay используют одинаковые Canonical classes и значения:

Trade | Quote | Candle

PostgreSQL reader создаёт новый Canonical объект, поэтому identity с первоначальным Live-объектом не обещается. После материализации Historical record и созданный из него Replay event используют один и тот же payload без повторного копирования.

Replay не создаёт ReplayTrade, не меняет source на replay и не восстанавливает transport document. Технический Replay envelope не объявляется существующим Live Runtime event-контрактом. Совместимость гарантируется на уровне точных Canonical payload classes и значений.

4.4. Гарантируемый порядок

Build гарантирует детерминированную event-time хронологию Canonical данных. Он не обещает точное повторение порядка исходных сетевых пакетов, потому что raw transport arrival log не сохраняется.

Оси времени:

Тип Historical Query Replay
Trade executed_at executed_at
Quote received_at received_at
Candle revision open_time observed_at

exchange_timestamp Quote не используется как Replay time, чтобы не создавать look-ahead. Candle revision воспроизводится в момент её наблюдения, а не задним числом в open_time.


5. Архитектурная граница и направление импортов

Canonical Models
      ↑              ↑
Historical contracts  Replay contracts
      ↑              ↑
PostgreSQL readers → ReplayPlan builder
                           ↓
                    ReplaySession
                           ↓
                    async consumer

Запрещённые зависимости:

access/replay → acquisition.runtime
access/replay → storage write facade
access/replay → bootstrap/application
access/replay → telegram/trading
access/replay → runtime_events/EventBus

Replay не вызывает TradeStreamConsistencyController. Иначе воспроизведение изменило бы durable history и operational checkpoint.


6. Historical contracts

6.1. Time range

Все публичные запросы используют timezone-aware полуоткрытый диапазон:

[start_time, end_time)

Время нормализуется в UTC. Требуется строгое start_time < end_time.

6.2. Records

Historical record хранит точный Canonical payload и технические поля, которые не должны добавляться в Canonical model:

TradeHistoryRecord
├── venue
├── trade: Trade
├── first_observed_at
├── last_observed_at
├── observation_sources
├── replay_sequence
└── canonical_schema_version

QuoteHistoryRecord
├── venue
├── quote: Quote
├── observation_sources
├── replay_sequence
└── canonical_schema_version

CandleRevisionHistoryRecord
├── venue
├── candle: Candle
├── observed_at
├── is_final
├── observation_sources
├── replay_sequence
└── canonical_schema_version

Records являются immutable, используют slots и не копируют Canonical payload.

6.3. Queries и pages

Каждый запрос содержит один venue, один canonical symbol, один UTC-диапазон и bounded page limit. Candle query дополнительно содержит case-sensitive interval.

Пустая страница является корректным результатом. Повреждённая строка, неизвестная версия Canonical schema или неверный enum являются integrity error; строка не пропускается молча.


7. Global replay sequence, ordering и keyset

7.1. Причина нового durable tie-breaker

Текущие таблицы не содержат подходящего immutable порядка Replay:

  • signed trade_id нельзя сортировать обычным числовым сравнением на границах rollover;
  • first_observed_at может измениться при обновлении provenance;
  • между разными типами данных нет общего устойчивого tie-breaker.

060.29.2 добавляет одну глобальную положительную replay_sequence для Trades, Quotes и Candle revisions.

Для новых строк sequence назначается только первой durable-вставке и не изменяется при duplicate/provenance update. Пропуски sequence после rollback допустимы: важен порядок, а не непрерывность значений.

7.2. Детерминированный backfill существующих строк

До migration 060.29.2 база не сохраняла global ordinal. Поэтому точный первоначальный transport, insertion или commit order существующих строк восстановить невозможно.

Backfill использует один явно фиксированный порядок:

Replay event_time
→ data_type rank: Trade=1, Quote=2, Candle revision=3
→ venue COLLATE "C"
→ symbol COLLATE "C"
→ полная durable identity типа

Полная identity:

Trade:           executed_at, trade_id
Quote:           received_at
Candle revision: interval COLLATE "C", open_time, observed_at

Этот порядок воспроизводим для одинакового committed dataset, но не выдаётся за исторический порядок сетевых пакетов. В частности, для уже существующих Trades с одинаковым executed_at backfill не может доказать первоначальный rollover order.

Для новых строк immutable sequence сохраняет порядок первой записи при одинаковом event time. Batch writer обязан назначать sequence в порядке входного Canonical batch, даже если блокировки строк берутся в другом безопасном порядке. Между конкурентными транзакциями global sequence остаётся устойчивым tie-breaker, но не объявляется commit chronology.

7.3. Stable order

Historical и Replay ordering используют:

(event_time, replay_sequence)

trade_id не входит в cursor и order key. Для новых строк одинаковый timestamp сохраняет durable insertion order на границах:

INT32_MAX → INT32_MIN
-1 → 0

Для backfilled строк порядок остаётся детерминированным по policy раздела 7.2, но не заявляется точным первоначальным rollover order.

7.4. Pagination

Historical Queries используют только forward keyset pagination. OFFSET запрещён.

Структурированный cursor содержит:

  • тип данных;
  • query scope;
  • исходный временной диапазон;
  • последнее event_time;
  • последнее replay_sequence;
  • version.

Cursor другого типа, symbol, venue, interval или окна отклоняется. Page limit можно менять между запросами с тем же cursor.

7.5. Shared sequence и durable schema

Migration 9 add_global_market_data_replay_sequence создаёт один общий PostgreSQL sequence:

market_data.replay_sequence
BIGINT
MINVALUE 1
CACHE 1
NO CYCLE
OWNED BY NONE

Отдельная registry/counter table не создаётся. Единственным распределителем ordinal для новых Trades, Quotes и Candle revisions является shared sequence. Его нельзя привязать через OWNED BY к одной колонке, потому что он обслуживает сразу три таблицы.

Каждая из таблиц получает колонку replay_sequence BIGINT, общий DEFAULT nextval(...), NOT NULL и именованный CHECK (> 0). После backfill изменение уже назначенного значения запрещает database trigger на partitioned parent. Тот же контракт обязан действовать в default, ранее созданных monthly и будущих partitions.

Один cross-table UNIQUE constraint для трёх partitioned tables в PostgreSQL недоступен. Глобальная уникальность обеспечивается shared sequence и контролируемыми repositories. Ручная вставка явно заданного replay_sequence вне этих adapters не входит в поддерживаемый контракт.

7.6. Блокирующая migration и атомарный backfill

Migration 9 выполняется существующим StorageMigrationRunner в одной transaction. Сначала runner владеет общим STORAGE_MIGRATION_ADVISORY_LOCK_ID. Затем первым statement migration 9, до любых table locks, берётся единый MARKET_DATA_PARTITION_ADVISORY_LOCK_ID. Этот же partition advisory lock используют PostgresMarketDataPartitionManager и PostgresMarketDataRetentionService.

Полный обязательный порядок блокировок:

STORAGE_MIGRATION_ADVISORY_LOCK_ID
→ MARKET_DATA_PARTITION_ADVISORY_LOCK_ID
→ ACCESS EXCLUSIVE trades
→ ACCESS EXCLUSIVE quotes
→ ACCESS EXCLUSIVE candle_revisions

Так migration, Partition Manager и Retention сериализуют partition DDL до захвата table locks. Ни один участник не может одновременно держать lock default partition и ждать parent table, пока migration держит parent и ждёт default. Это исключает deadlock parent ↔ default.

После двух advisory locks для parent tables и их partitions рекурсивно берётся ACCESS EXCLUSIVE lock в одном порядке:

trades → quotes → candle_revisions

Блокировка запрещает конкурентные writes и partition DDL до завершения migration. Так новая строка не может появиться между построением глобального порядка, установкой defaults и включением ограничений.

Backfill строит transaction-local mapping через один UNION ALL всех трёх типов и назначает row_number() по policy раздела 7.2. Обновление строк выполняется по полной durable primary identity. Использование ctid и зависимость от физического порядка строк или порядка вычисления nextval() запрещены.

После backfill sequence переводится за максимальное назначенное значение. Для пустого dataset следующий вызов возвращает 1. Только после этого устанавливаются DEFAULT, NOT NULL, positive checks, immutable triggers и partitioned keyset indexes:

Trades:  (venue, symbol, executed_at, replay_sequence)
Quotes:  (venue, symbol, received_at, replay_sequence)
Candles: (venue, symbol, interval, open_time, replay_sequence)
Candles replay preparation:
          (venue, symbol, interval, observed_at, replay_sequence)

Существующий Trade index с trade_id в migration 9 не удаляется. Ошибка любого statement откатывает schema, backfill и запись версии 9; повторный запуск начинается с согласованного состояния.

Внимание: migration 9 является блокирующей. Её длительность пропорциональна числу сохранённых строк, стоимости backfill и созданию indexes. Перед применением к большой production database обязательны замер времени и дополнительного дискового места на её актуальной копии, проверенный backup, rollback plan и согласованное maintenance window. Online staged migration с chunked backfill, concurrent indexes и поэтапным включением ограничений в 060.29.2 не реализуется. Если она понадобится, это будет отдельное архитектурное и deployment-решение.

7.7. Совместимость writers

Single Trade, атомарная запись Trade с checkpoint, Quote и Candle используют database default. Duplicate либо provenance update сохраняет первоначальный replay_sequence и не включает эту колонку в UPDATE.

Trade batch сначала полностью валидируется. Затем writer назначает по одному sequence каждому элементу строго в исходном Canonical tuple. Только связанные пары (prepared_trade, replay_sequence) сортируются по существующему identity lock order и записываются. Поэтому безопасный порядок захвата row locks не меняет порядок событий внутри исходного batch, включая INT32_MAX → INT32_MIN и -1 → 0.

Sequence не является transaction counter: duplicate, conflict или rollback могут оставить пропуск. Между конкурентными transactions он показывает порядок выдачи ordinal, но не обещает commit chronology.

7.8. PostgreSQL Trade History reader

060.29.2 добавляет отдельный synchronous provider-based adapter PostgresTradeHistoryRepository. Он реализует только TradeHistoryReaderProtocol, не расширяет write facade, не читает checkpoint и не владеет lifecycle PostgreSQL pool.

Query использует один parameterized SELECT, полуоткрытый фильтр [start_time, end_time), forward keyset predicate (executed_at, replay_sequence) > (...), тот же ascending ORDER BY и LIMIT query.limit + 1. OFFSET, SELECT *, FOR UPDATE и любые writes запрещены. next_cursor указывает на последний возвращённый record только тогда, когда получена лишняя строка.

Reader явно проецирует все Canonical и provenance columns и строго проверяет signed Trade ID, Decimal values, aggressor side, UTC times, Canonical schema version, observation sources, query scope и positive sequence. Повреждённая строка не пропускается.

Обычная страница является текущим committed view одного statement. Repository не выполняет commit, rollback и не меняет isolation level: transaction принадлежит connection provider. Это позволяет обычным Historical callers явно владеть границей чтения. 060.29.3 повторно использует тот же строгий row mapper, но строит Replay через отдельный snapshot query внутри короткой READ ONLY REPEATABLE READ transaction.

Неверный тип query отклоняется до получения connection как MarketDataAccessValidationError. Повреждённая строка или page invariant дают MarketDataAccessIntegrityError; backend/provider error даёт MarketDataAccessOperationError с исходной причиной. Существующие Access errors не переоборачиваются, а KeyboardInterrupt и SystemExit не перехватываются.

7.9. PostgreSQL Quote/Candle History readers

060.29.3 добавляет независимые PostgresQuoteHistoryRepository и PostgresCandleRevisionHistoryRepository. Они используют тот же provider-based lifecycle и ту же строгую политику ошибок, что и Trade History reader, но сохраняют собственные публичные оси истории:

Quote:           (received_at, replay_sequence)
Candle revision: (open_time, replay_sequence)

Quote query использует полуоткрытый диапазон received_at и явно проецирует Canonical payload, provenance, global sequence и schema version. Candle query дополнительно требует один case-sensitive interval и использует полуоткрытый диапазон open_time. Оба reader используют forward keyset pagination и LIMIT query.limit + 1.

Строгая материализация PostgreSQL-строк вынесена в общий внутренний модуль. Trade, Quote, Candle History и Replay Plan Builder поэтому одинаково проверяют Canonical values, UTC, provenance, schema version и positive global sequence. Извлечение Trade-маппера не меняет публичное поведение принятого 060.29.2.

MarketDataHistoricalAccess является DB-neutral фасадом трёх reader protocols. Его создание не выполняет I/O; фасад не владеет pool и не переоборачивает ошибки конкретного reader.


8. Snapshot и concurrent writes

Historical pages являются текущим committed view. Между страницами Live/Recovery могут добавить строки, а Retention может удалить данные. Поэтому обычная пагинация не обещает межстраничный snapshot.

Replay не читает медленно меняющиеся страницы во время playback. Сначала в одной короткой транзакции:

READ ONLY
REPEATABLE READ

материализуется bounded ReplayPlan. После этого cursor, transaction и connection закрываются, а Replay работает только с immutable tuple в памяти.

Долговременная PostgreSQL transaction на период виртуального времени запрещена. Превышение max_records является явной ошибкой, а не частичным либо молчаливо обрезанным Replay.

8.1. Отдельный PostgreSQL Replay snapshot query

Публичный Candle History query нельзя использовать как источник Replay: он выбирает свечи по open_time, тогда как конкретная ревизия должна воспроизводиться по observed_at.

PostgresReplayPlanBuilder выполняет один отдельный parameterized UNION ALL query только для запрошенных типов:

Trade           → replay_at = executed_at
Quote           → replay_at = received_at
Candle revision → replay_at = observed_at

Static SQL branches выбираются только по ReplayDataType; venue, symbols, временной диапазон и intervals всегда передаются параметрами. Итоговый query сортирует все типы по (replay_at, replay_sequence) и запрашивает max_records + 1 строк. Лишняя строка вызывает ReplayPlanLimitExceededError; частичный plan не создаётся.

Builder владеет только короткими cursor и transaction внутри одного вызова create_plan(). Первой SQL-командой transaction устанавливается:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY;

Pool и connection provider принадлежат приложению. После materialization transaction и connection закрыты, а возвращённый ReplayPlan не зависит от PostgreSQL. Повреждённые строки дают MarketDataAccessIntegrityError, backend/provider failures — MarketDataAccessOperationError с исходной причиной, превышение лимита остаётся отдельной Replay-ошибкой. BaseException не перехватывается.


9. Replay contracts

9.1. ReplayEvent и ReplayPlan

ReplayEvent содержит:

  • venue;
  • UTC replay_at;
  • immutable replay_sequence;
  • точный Canonical payload;
  • is_final только для Candle revision.

ReplayPlan хранит исходный immutable request и полностью материализованный tuple событий. Пустой plan является допустимым no-op. Непустой plan строго возрастает по (replay_at, replay_sequence) и не содержит повторяющихся global sequence.

9.2. Clock

Контракты разделяются:

  • MarketDataClockProtocol — только чтение текущего времени;
  • ReplayClockProtocol — управляемое advance_to().

Одна Replay Session владеет одними Virtual Clock. Стартовое время равно request.start_time, включая пустой plan. Время не может уменьшаться.

060.29.4 добавляет concrete DeterministicReplayClock с единственным изменяемым состоянием — текущим UTC-временем. Clock не читает системное время, не выполняет sleep, I/O и не создаёт задач.

Начальное значение и каждый advance_to() принимают timezone-aware datetime, совместимый с уже принятыми Replay time contracts, и канонизируют его в обычный UTC datetime. Naive datetime запрещён.

Переход вперёд изменяет now. Переход на тот же абсолютный момент разрешён как идемпотентный no-op: несколько событий могут иметь один replay_at и различаться только replay_sequence. Только переход назад вызывает ReplayClockError; после любой ошибки состояние не меняется.

Clock не знает о ReplayPlan, sequence и верхней границе request. У него нет reset(), advance_by(), start() и stop(). Один экземпляр принадлежит одной Session и не разделяется между Session или OS threads. Внутренние lock и owner registry не добавляются; управление последовательно и синхронно выполняет будущая Replay Session.

9.3. Consumer и Session

Одна Session имеет одного async consumer. Доставка выполняется строго последовательно:

clock.advance_to(event.replay_at)
        ↓
await consumer.consume(event)

Fan-out, fire-and-forget и error isolation в Build не добавляются.

060.29.5 добавляет concrete ReplaySession. Отдельный класс ReplayEngine не создаётся: engine является последовательным циклом внутри ReplaySession.run().

Конструктор получает уже материализованный точный ReplayPlan, один ReplayClockProtocol и один ReplayConsumerProtocol. Clock передаётся Session в исключительное владение и до запуска обязан находиться в каноническом UTC-времени plan.request.time_range.start_time. Несовпадение считается ошибкой dependency и даёт MarketDataReplayValidationError; Session не выполняет скрытый reset.

Plan сохраняется по identity и может использоваться для создания других независимых Session. Public clock предоставляет consumer-facing границу MarketDataClockProtocol, а lifecycle consumer остаётся у caller/composition.

Session является one-shot и имеет состояния:

CREATED → RUNNING → COMPLETED
                  ↘ FAILED
                  ↘ CANCELLED

Повторный, конкурентный или рекурсивный run() запрещён. Session переводится в RUNNING до первого await, поэтому внутри одного event loop второй caller немедленно получает ReplaySessionStateError без lock и без влияния на первый запуск. Для повторения создаются новые Session и Clock.

Empty plan штатно проходит CREATED → RUNNING → COMPLETED, не вызывает Clock или consumer и оставляет время в request.start_time. run() не возвращает Result DTO или progress: успешный результат равен None.

9.4. Consumer Factory и Composition

060.29.6 добавляет только две новые публичные границы:

ReplayConsumerFactoryProtocol
ReplaySessionFactory.prepare_session(request)

ReplayConsumerFactoryProtocol синхронно создаёт отдельный consumer для одной Session и получает:

  • точный immutable ReplayPlan;
  • Clock этой же Session через read-only MarketDataClockProtocol.

Factory обязана возвращать новый stateful consumer при каждом вызове, не выполнять I/O, не создавать task и не запускать lifecycle consumer. Default/no-op consumer не предоставляется: конкретный consumer выбирает вызывающая подсистема с реальным use case.

ReplaySessionFactory получает только ReplayPlanBuilderProtocol и ReplayConsumerFactoryProtocol. Конструктор сохраняет зависимости и не выполняет I/O. Явный prepare_session() выполняет строго одну последовательность:

exact ReplayPlanRequest validation
        ↓
plan_builder.create_plan(request)
        ↓
exact ReplayPlan + plan.request identity validation
        ↓
fresh DeterministicReplayClock(request.start_time)
        ↓
consumer_factory.create_consumer(plan, тот же Clock)
        ↓
fresh ReplaySession(plan, тот же Clock, consumer)

Метод prepare_session() намеренно является синхронным и потенциально блокирующим: concrete PostgreSQL builder выполняет чтение snapshot в потоке вызывающего кода. Composition не скрывает SQL через asyncio.to_thread(), потому что cancellation asyncio task не остановит уже выполняющийся SQL в worker thread. Выбор отдельного worker остаётся у внешнего caller.

Возвращается обычная ReplaySession в состоянии CREATED. Composition не вводит Result DTO, отдельный ReplayEngine, service, registry, cache, lock или single-flight. Caller отдельно и явно выполняет await session.run() и владеет этой coroutine/task.

Каждый вызов prepare_session() создаёт новый Clock, consumer и Session. Один immutable plan может быть повторно возвращён injected builder, но изменяемые части графа между Session не разделяются. Один и тот же concrete Clock по identity передаётся consumer factory через read-only Protocol и Session через управляющий Protocol.

Composition не сериализует параллельные вызовы. Thread-safety общего injected builder и consumer factory является их внешним контрактом; два уже созданных графа не разделяют изменяемые Session/Clock/consumer.

Ошибки builder и consumer factory распространяются без обёртки, retry и частичного результата. Неверный тип request, неверный exact plan или подмена plan.request дают MarketDataReplayValidationError до создания следующей зависимости. Class objects и async implementations вместо синхронных dependency instances отклоняются при создании composition.

060.29.6 не меняет Bootstrap, settings, SQL, PostgreSQL pool, Runtime, Telegram или startup приложения. Подготовка выполняется только после явного вызова prepare_session(), а playback — только после явного вызова run().


10. Lifecycle, cancellation и ошибки

  1. Создание contracts, access facade и ReplaySessionFactory не выполняет I/O; явный prepare_session() является blocking-границей.
  2. Historical readers используют существующий managed pool, но не открывают и не закрывают его.
  3. ReplayPlan полностью отделяется от PostgreSQL до playback.
  4. Caller владеет coroutine task ReplaySession.run().
  5. Session не создаёт скрытую root/background task.
  6. Consumer error распространяется вызывающему коду и останавливает доставку следующих событий.
  7. CancelledError не проглатывается.
  8. Clock error является terminal для Session.
  9. Частично доставленный consumer prefix нельзя транзакционно отменить; повтор выполняется новой Session по явному решению caller.
  10. Clock переводится до consumer; после consumer failure или cancellation остаётся во времени начатого события.
  11. Исходные error и cancellation распространяются без обёртки; Session только фиксирует FAILED или CANCELLED.
  12. Session не создаёт task, lock, connection, retry или cleanup и не управляет lifecycle consumer.

11. Scope 060.29.0060.29.1

060.29.0

  • этот architecture document;
  • read/write boundary;
  • event-time и ordering policy;
  • global replay sequence policy;
  • snapshot, resource и failure policy;
  • разбиение Build и acceptance criteria.

060.29.1

  • immutable Historical records;
  • time range, queries, typed cursors и pages;
  • runtime-checkable Historical reader protocols;
  • отдельная error hierarchy;
  • Replay request, event, materialized plan и session state;
  • clock, consumer, plan builder и session Protocol-контракты;
  • DB-neutral unit-тесты.

060.29.0060.29.1 не выполняют SQL, network I/O, filesystem I/O и не создают asyncio tasks.


12. Следующие подэтапы

060.29.2

  • migration 9 с shared global replay_sequence без registry table;
  • блокирующий атомарный backfill существующих строк;
  • immutable sequence и partitioned keyset indexes;
  • совместимость single/batch writers без изменения checkpoint semantics;
  • отдельный PostgreSQL Trade Historical reader;
  • unit- и opt-in PostgreSQL verification.

Детальный утверждённый контракт закреплён в разделах 7.57.8.

060.29.3

  • Quote History по (received_at, replay_sequence);
  • Candle History по (open_time, replay_sequence) и одному interval;
  • общий DB-neutral Historical Access facade;
  • единые строгие PostgreSQL row mappers;
  • отдельный snapshot query с Candle replay axis observed_at;
  • короткая READ ONLY REPEATABLE READ transaction;
  • bounded immutable ReplayPlan и fail-fast max_records + 1.

060.29.4

  • concrete DeterministicReplayClock;
  • UTC normalization без чтения wall clock;
  • разрешённый равный переход и запрет движения назад;
  • single-owner policy без lock, reset и lifecycle;
  • public export и DB-neutral unit-тесты.

060.29.5

  • concrete ReplaySession без отдельного ReplayEngine;
  • injected Clock/consumer и fail-fast проверка начального времени;
  • one-shot lifecycle и последовательный playback loop;
  • error/cancellation propagation без retry и rollback;
  • DB-neutral async unit-тесты и public export.

060.29.6

  • ReplayConsumerFactoryProtocol для обязательного concrete consumer;
  • единый ReplaySessionFactory.prepare_session();
  • явная blocking composition request → plan → fresh Clock → fresh consumer → fresh Session;
  • один Clock по identity для Session и consumer через разные Protocol-границы;
  • отсутствие default consumer, скрытого to_thread, cache и автоматического startup;
  • DB-neutral unit-тесты composition, identity, ошибок и конкурентности.

060.29.7

  • verification-only integration без предварительного production diff;
  • существующий opt-in harness только для локальной dzentra_test_* БД;
  • локальный путь Loopback Trade Runtime → Storage → Historical Access → Replay;
  • mixed-type PostgreSQL snapshot и caller-owned playback;
  • реальная граница READ ONLY REPEATABLE READ при concurrent commit;
  • возврат PostgreSQL resources до первого consumer call;
  • limit, integrity, provider/backend и consumer failure paths;
  • независимые графы двух concurrent blocking preparations;
  • десятикратный повтор нового PostgreSQL target.

060.29.8

  • обязательный Pyright gate, compileall и проверка зависимостей;
  • расширенный Access/Replay/Storage/migration unit-набор;
  • десятикратный PostgreSQL Replay target;
  • полный PostgreSQL Storage и общий integration-наборы;
  • отдельная opt-out проверка без подключения к базе;
  • fixed stress и полная offline-регрессия;
  • итоговый read-only review архитектуры, SQL, ordering, lifecycle, обработки ошибок, освобождения ресурсов и документации.

Подэтап остаётся verification-only. Dzengi live и acquisition soak не являются обязательными: Build не добавляет live endpoint wiring или долгоживущие Replay-задачи, а детерминированный путь Loopback Runtime → Storage → Historical Access → Replay проверен на локальной PostgreSQL.


13. Test strategy

Обязательная статическая типизация

  • Pyright 1.1.411 запускается в режиме standard, совпадающем с используемым Pylance;
  • каноническая команда — scripts/check_python_types.sh;
  • допустимый результат — только 0 errors, 0 warnings;
  • проверяются Market Data, Storage, Bootstrap, Runtime Events и все их unit/integration/stress/live/support-тесты;
  • gate входит в обычную offline pytest-регрессию через отдельный static-тест и поэтому не может быть пропущен при приёмке Build;
  • type: ignore, отключение диагностик или ослабление режима не используются для исправления новых ошибок;
  • область проверки в следующих Build может только расширяться.

Contracts

  • frozen/slots;
  • строгие типы, включая запрет bool вместо int;
  • UTC normalization и запрет naive datetime;
  • полуоткрытые временные границы;
  • canonical symbol, venue и case-sensitive Candle interval;
  • provenance и Canonical schema version;
  • cursor scope/version;
  • typed pages и строгий order key;
  • runtime-checkable Protocols.

Ordering

  • одинаковое event time и разные sequence;
  • INT32_MAX → INT32_MIN;
  • -1 → 0;
  • provenance update не меняет sequence;
  • размер страницы не меняет итоговый порядок.

Migration и writers

  • детерминированный mixed-type backfill независимо от physical order;
  • shared sequence во всех parent/default/existing/future partitions;
  • direct update sequence запрещён trigger;
  • duplicate и provenance update сохраняют первоначальный ordinal;
  • batch input order сохраняется независимо от identity lock order;
  • concurrent migration callers, writer wait и partition-manager race;
  • partial failure откатывает migration 9, clean retry проходит;
  • gaps после duplicate и transaction rollback считаются корректными.

PostgreSQL Trade History

  • start inclusive и end exclusive;
  • empty, exact-limit и extra-row pages;
  • смена page limit не создаёт skips или duplicates;
  • equal timestamp и signed rollover упорядочиваются по sequence;
  • default и monthly partitions дают один стабильный результат;
  • повреждённая строка и неизвестная schema version не пропускаются;
  • DB failure сохраняет cause и не оставляет connection/cursor;
  • чтение не меняет Trade, provenance, checkpoint или sequence.

PostgreSQL Quote/Candle History и Replay snapshot

  • Quote start inclusive и end exclusive по received_at;
  • Candle History start inclusive и end exclusive по open_time;
  • case-sensitive Candle interval и устойчивые keyset pages;
  • строгая проверка Quote prices и Candle OHLCV/revision metadata;
  • Candle с open_time вне Replay range и observed_at внутри включается;
  • Candle с open_time внутри и observed_at вне исключается;
  • static branches включают только запрошенные data types;
  • один global order для одинакового времени разных типов;
  • max_records + 1 даёт ошибку без частичного plan;
  • transaction mode, cleanup, backend cause и BaseException проверены.

Replay lifecycle

  • Protocol, slots, initial CREATED и read-only properties;
  • fail-fast dependency validation и точное начальное время Clock;
  • empty plan без вызовов Clock/consumer;
  • точный порядок tuple, Clock-before-consumer и event identity;
  • одинаковое replay_at с разными global sequence;
  • последовательная backpressure без параллельной доставки;
  • consumer/Clock failure и исходная error identity;
  • cancellation без доставки suffix и без проглатывания;
  • concurrent/repeated/reentrant run() rejection;
  • независимые Session и Clock при общем immutable plan;
  • отсутствие hidden tasks, retries, cleanup и PostgreSQL connections.

Deterministic Replay Clock

  • начальное UTC-время и normalization разных UTC offsets;
  • запрет non-datetime и naive datetime;
  • прямой переход с сохранением микросекунд;
  • повторный переход на тот же абсолютный момент;
  • несколько событий с одинаковым replay_at;
  • обратный переход через ReplayClockError без изменения now;
  • независимость Clock разных Session;
  • read-only now, __slots__ и отсутствие reset/lifecycle/tasks/I/O.

Consumer и Composition

  • runtime-checkable ReplayConsumerFactoryProtocol и public exports;
  • fail-fast constructor validation, включая class objects и async implementations;
  • exact request до builder и identity request в созданном plan;
  • порядок builder → Clock → consumer → Session;
  • один Clock по identity для consumer factory и Session;
  • новый Clock, consumer и Session при каждом вызове;
  • пустой plan без автоматического запуска consumer;
  • blocking preparation в caller thread без скрытого to_thread;
  • ошибки builder/factory по identity, без retry и wrapping;
  • отсутствие hidden tasks, lifecycle calls, cache и automatic startup;
  • независимые графы для двух concurrent callers.

PostgreSQL Replay and Failure Verification

  • opt-in отключён без точного флага и явного локального DSN;
  • destructive reset разрешён только для проверенной dzentra_test_* БД;
  • local Loopback Trade проходит Runtime, Storage, History и Replay;
  • Trade, Quote и Candle revisions воспроизводятся в global order;
  • empty snapshot остаётся штатным no-op;
  • test-only barrier фиксирует snapshot до concurrent commit без sleep;
  • первая Session не видит post-snapshot commit, следующая видит его;
  • pool с max_size=1 освобождается до playback и может быть переоткрыт;
  • limit/integrity/provider/backend errors не создают consumer или Session;
  • consumer failure/cancellation происходят после освобождения DB;
  • Replay не меняет Market Data или persistent checkpoint;
  • два blocking caller реально участвуют и получают отдельные графы;
  • threads, tasks, cursors, transactions и connections закрываются;
  • новый target проходит однократно и десять раз с ResourceWarning как error.

14. Вне scope

  • raw exchange documents и точный transport arrival log;
  • загрузка рыночной истории до первого запуска persistent storage;
  • автоматический historical backfill;
  • пользовательские ордера, fills, позиции и private account operations;
  • аналитические вычисления и Feature Engineering;
  • стратегии, Risk, Portfolio и Order Management;
  • полноценный Backtesting;
  • wall-clock pacing, speed, pause, seek и resume;
  • автоматический Retention Scheduler;
  • persistent Quote/Candle production consumers;
  • distributed lease, leader election и HA failover;
  • Telegram/HTTP public API;
  • автоматический запуск Replay вместе с приложением;
  • online staged migration global sequence без maintenance window.

15. Граница доступного периода

После 060.29 база предоставляет запросы и Replay только для данных, которые действительно находятся в PostgreSQL.

Начальная граница production Trade history:

первый успешный запуск
MARKET_DATA_STORAGE_ENABLED=true
и включённого Production Trade Stream

060.28 заполняет bounded downtime gap после уже известного persistent checkpoint, если нужные Trades ещё доступны в REST API Dzengi. Он не загружает всю историю до первого checkpoint.

Конечная граница Trade history постоянно продвигается работающим Runtime. Retention по умолчанию выключен, поэтому программного срока удаления истории нет. Фактический период всё равно ограничен:

  • временем первого production-запуска storage;
  • периодами, когда Runtime или база были недоступны;
  • глубиной истории, доступной Recovery API;
  • ручной очисткой, будущей Retention policy и объёмом диска.

Для Quotes и Candle revisions production consumers пока не подключены. Historical Access возвращает только строки, фактически записанные через их repositories; полнота production Quote/Candle history не заявляется.

Каждая Historical page отражает committed-состояние на время собственного запроса. Последовательная pagination даёт доступ ко всем фактически сохранённым строкам диапазона только при неизменном dataset между страницами. Live/Recovery-запись и Retention могут изменить этот набор; единый межстраничный snapshot и полная биржевая история без отдельного completeness metadata не гарантируются. Initial backfill потребует нового согласованного Build.


16. Acceptance criteria

060.29.0060.29.1 могут быть приняты, если:

  1. Read contracts не меняют write-only Storage API.
  2. Нет SQL, psycopg, repository, Runtime или Bootstrap implementation.
  3. Все public value objects immutable и slotted.
  4. Query windows aware, UTC и half-open.
  5. Cursor строго связан с query scope.
  6. trade_id не входит в Historical order key.
  7. Records и Replay events сохраняют identity Canonical payload.
  8. ReplayPlan строго упорядочен и bounded.
  9. Clock/Consumer/Session представлены только Protocol-контрактами.
  10. Комментарии и docstrings новых файлов написаны по-русски.
  11. Целевые unit-тесты проходят.
  12. Отдельный read-only review не выявляет findings.

060.29.2 может быть принят, если:

  1. Migration 9 атомарно и детерминированно заполняет global sequence.
  2. Shared sequence является единственным allocator; registry table нет.
  3. Все partitions получают positive NOT NULL sequence и keyset indexes.
  4. Provenance update и duplicate не меняют уже записанный ordinal.
  5. Trade batch сохраняет input order до identity lock sorting.
  6. Migration concurrency, blocking и rollback проверены на PostgreSQL.
  7. Trade History использует half-open range и forward keyset pagination.
  8. Reader строго проверяет строки и сохраняет error cause.
  9. Reader не владеет pool lifecycle и не меняет Runtime/Bootstrap.
  10. Unit, PostgreSQL target и полная регрессия проходят.
  11. Отдельный acceptance read-only review не выявляет findings.

060.29.3 может быть принят, если:

  1. Quote History использует received_at и forward keyset pagination.
  2. Candle History использует open_time и case-sensitive interval.
  3. Все три reader используют единые строгие PostgreSQL row mappers.
  4. Historical Access facade не выполняет I/O и не владеет pool.
  5. Replay Builder использует отдельный snapshot query, а не Candle History pagination.
  6. Candle Replay фильтруется по observed_at, а не по open_time.
  7. Snapshot materializes в одной короткой READ ONLY REPEATABLE READ transaction и закрывает все ресурсы до возврата plan.
  8. SQL branches статичны, а пользовательские значения параметризованы.
  9. Итоговый порядок строго возрастает по (replay_at, replay_sequence) для всех типов.
  10. max_records + 1 даёт ReplayPlanLimitExceededError без частичного результата.
  11. Unit, PostgreSQL target и полная регрессия проходят.
  12. Отдельный acceptance read-only review не выявляет findings.

060.29.4 может быть принят, если:

  1. Clock начинается с переданного aware времени и хранит его в UTC.
  2. Clock не читает wall clock и не выполняет I/O или sleep.
  3. advance_to() разрешает прямой и равный переход.
  4. Обратный переход даёт ReplayClockError и не меняет now.
  5. Ошибка типа или naive datetime не меняет состояние.
  6. Один Clock не содержит shared Session state или внутренних lock.
  7. Нет reset, relative advance, lifecycle и скрытых asyncio tasks.
  8. Clock соответствует MarketDataClockProtocol и ReplayClockProtocol.
  9. В подэтапе нет Replay Session, consumer delivery или composition.
  10. Unit и полная регрессия проходят.
  11. Отдельный acceptance read-only review не выявляет findings.

060.29.5 может быть принят, если:

  1. Concrete ReplaySession соответствует ReplaySessionProtocol и не вводит отдельный ReplayEngine.
  2. Exact ReplayPlan, ReplayClockProtocol и ReplayConsumerProtocol проверяются до первого запуска.
  3. Clock начинается точно в каноническом UTC plan.request.time_range.start_time; reset не выполняется.
  4. run() переводит CREATED → RUNNING до первого await и допускает только один запуск.
  5. Для каждого события Clock продвигается до одного последовательного await consumer.consume(event).
  6. Empty plan завершается без вызовов dependency и не меняет Clock.
  7. Ошибка переводит Session в FAILED, cancellation — в CANCELLED, а исходный объект исключения распространяется без обёртки.
  8. Повторный, конкурентный и рекурсивный запуск дают ReplaySessionStateError, не повреждая активный или terminal state.
  9. Session не создаёт tasks, locks, I/O, retries, rollback или cleanup.
  10. В подэтапе нет Plan Builder composition, выбора consumer, Bootstrap или автоматического startup.
  11. Unit и полная регрессия проходят.
  12. Отдельный acceptance read-only review не выявляет findings.

060.29.6 может быть принят, если:

  1. ReplayConsumerFactoryProtocol является единственной новой границей выбора concrete consumer.
  2. ReplaySessionFactory проверяет синхронные dependency instances и не выполняет I/O или создание объектов графа в конструкторе.
  3. prepare_session() является синхронной blocking-границей и не использует скрытый to_thread или background task.
  4. Builder получает точный исходный request ровно один раз, а результат обязан быть точным ReplayPlan с тем же request по identity.
  5. Порядок сборки равен plan → Clock → consumer → Session.
  6. Consumer factory и Session получают один Clock по identity через разные Protocol-границы.
  7. Каждый вызов создаёт новые Clock, consumer и Session без cache, registry, lock и single-flight.
  8. Подготовленная Session остаётся в CREATED; consumer, lifecycle и playback не запускаются автоматически.
  9. Default/no-op consumer, отдельный ReplayEngine, Result DTO, Bootstrap и settings не добавлены.
  10. Ошибки dependencies и cancellation распространяются без обёртки, retry и частично возвращённой Session.
  11. Unit и полная offline-регрессия проходят.
  12. Отдельный acceptance read-only review не выявляет findings.

060.29.7 может быть принят, если:

  1. Подэтап остаётся verification-only, если тесты не выявили реальный production defect.
  2. Используется существующий opt-in PostgreSQL harness с проверкой локального endpoint и имени dzentra_test_*.
  3. Local Trade проходит Production Runtime, PostgreSQL Storage, Historical Access, ReplayPlan, Composition и Session.
  4. Mixed Trade/Quote/Candle plan воспроизводится в точном global order, а consumer наблюдает уже продвинутое время Clock.
  5. Реальный REPEATABLE READ snapshot не видит commit после зафиксированной точки, а следующая Session видит его полностью.
  6. PostgreSQL cursor, transaction и connection освобождены до первого consumer call; подготовленная Session работает после закрытия pool.
  7. Limit, integrity, provider/backend errors не создают consumer или частичную Session, а pool после контролируемой ошибки остаётся либо снова становится пригодным к работе.
  8. Consumer failure и cancellation сохраняют принятый lifecycle Session без удержания PostgreSQL resources и изменения durable данных.
  9. Два concurrent caller действительно выполняют real preparation и получают разные Plan, Clock, consumer и Session.
  10. Нет незавершённых PostgreSQL resources, threads или asyncio tasks.
  11. Обязательный Pyright gate проходит с нулём errors и warnings.
  12. Новый target проходит один раз и десять раз, полный PostgreSQL Storage integration, Replay unit и offline regression проходят.
  13. Отдельный acceptance read-only review не выявляет findings.

060.29.8 может быть принят, если:

  1. Pyright завершается с 0 errors, 0 warnings, static pytest gate, compileall и pip check проходят.
  2. Расширенный Access/Replay/Storage/migration unit-набор проходит.
  3. Двенадцать PostgreSQL Replay-сценариев стабильно проходят десять последовательных запусков с ResourceWarning как ошибкой.
  4. Полный PostgreSQL Storage и общий integration-наборы проходят с ResourceWarning как ошибкой.
  5. Без opt-in flag и DSN весь PostgreSQL Storage-набор пропускается и не устанавливает соединение с базой.
  6. Fixed stress и полная offline-регрессия проходят.
  7. Нет необъяснённых skips, незавершённых tasks, threads, cursors, transactions или PostgreSQL connections.
  8. Replay не изменяет Canonical Market Data, operational checkpoint или production lifecycle.
  9. Итоговые read-only review архитектуры, SQL, тестов и документации не выявляют findings P0P3.
  10. Подэтап не меняет production-код, если матрица не воспроизвела реальный дефект.

17. ADR

ADR-060.29-001 — Read API не расширяет write-only Storage facade

Historical Access получает отдельные DB-neutral contracts и adapters.

ADR-060.29-002 — Replay использует event-time chronology

Порядок исходных transport packets не заявляется без raw arrival log.

ADR-060.29-003 — Global immutable replay sequence

Один PostgreSQL sequence без registry table применяется ко всем Canonical Market Data и не изменяется при provenance update.

ADR-060.29-004 — Replay materializes bounded snapshot

PostgreSQL transaction завершается до начала виртуального playback.

ADR-060.29-005 — Canonical payload не заменяется Replay DTO

Replay envelope сохраняет точный Trade, Quote или Candle.

ADR-060.29-006 — Session является one-shot и caller-owned

Session не создаёт скрытую задачу и не скрывает consumer/cancellation errors.

ADR-060.29-007 — Migration 9 использует maintenance window

060.29.2 выбирает атомарную блокирующую migration. Online staged rollout для большой production database требует отдельного решения.

ADR-060.29-008 — Candle History и Replay используют разные оси

Публичная Candle History выбирает ревизии по open_time. Replay Plan Builder использует отдельный snapshot query и выбирает те же ревизии по observed_at, то есть в момент, когда они фактически стали известны.

ADR-060.29-009 — Replay engine находится внутри one-shot Session

060.29.5 не вводит отдельный ReplayEngine. Последовательный playback выполняет ReplaySession.run(), а создание plan, fresh Clock и consumer остаётся явной задачей Composition 060.29.6.

ADR-060.29-010 — Composition разделяет preparation и playback

060.29.6 использует один синхронный blocking ReplaySessionFactory.prepare_session() без скрытого worker и автоматического startup. Каждый вызов создаёт новый Clock, обязательный consumer и ReplaySession; caller отдельно владеет session.run().

ADR-060.29-011 — PostgreSQL acceptance остаётся verification-only

060.29.7 не добавляет production consumer или новые runtime-границы. Он проверяет уже принятые Storage, Historical, snapshot, Composition и Session на локальной PostgreSQL. Production-код может измениться только при воспроизводимом нарушении принятого контракта и только вместе с минимальным regression-тестом; test hooks в production запрещены.


18. Test evidence

Итоговая приёмочная проверка 060.29.0060.29.1:

Historical Access + Replay unit target: 111 passed
Full offline regression:                2416 passed, 69 deselected
Historical Access exact-type re-review: clean
Replay exact-type re-review:            clean
Combined acceptance read-only review:   clean

Подэтапы 060.29.0060.29.1 приняты. На этом этапе Build 060.29 оставался In Progress.

Итоговая приёмочная проверка 060.29.2:

Target unit:                              227 passed
Full Market Data Storage unit:            290 passed
New 060.29.2 PostgreSQL integration:       12 passed
Full PostgreSQL Storage integration:       63 passed
Full integration with PostgreSQL:          76 passed
Full offline regression:                   2461 passed, 81 deselected
Compileall через временный pycache:       clean
Migration acceptance read-only review:    clean
Reader/writer acceptance read-only review: clean
Повторный целевой набор review:             90 passed

Подэтапы 060.29.2060.29.3 приняты. На этом этапе Build 060.29 оставался In Progress.

Итоговая приёмочная проверка 060.29.3:

Historical Access + Replay unit target: 270 passed
Expanded Access/Replay/Storage unit:      446 passed
New 060.29.3 PostgreSQL integration:        2 passed
Full PostgreSQL Storage integration:        65 passed
Full integration with PostgreSQL:           78 passed
Full offline regression:                  2581 passed, 83 deselected
Compileall через временный pycache:        clean
Повторный целевой unit-набор:              270 passed
Formal acceptance read-only review:        clean

Подэтапы 060.29.3060.29.4 приняты после отдельных формальных приёмочных read-only review.

Проверка реализации Clock 060.29.4:

Deterministic Replay Clock unit target:  22 passed
Full Replay unit target:                100 passed
Historical Access + Replay unit target: 292 passed
Full offline regression:               2603 passed, 83 deselected
Compileall через временный pycache:      clean
git diff --check:                        clean
Короткий post-fix read-only review:      clean
Formal acceptance read-only review:      clean

Контракт и реализация 060.29.5 приняты после исправления fail-fast проверки class objects и чистого повторного read-only review.

Проверка реализации Replay Session 060.29.5:

Replay Session unit target:              34 passed
Full Replay unit target:                134 passed
Historical Access + Replay unit target: 326 passed
Full offline regression:               2637 passed, 83 deselected
Compileall через временный pycache:      clean
git diff --check:                        clean
Короткий implementation read-only review: clean
Post-fix acceptance read-only review:    clean

Проверка реализации Consumer and Composition Integration 060.29.6:

Replay Session Factory unit target:      43 passed
Full Replay unit target:                177 passed
Historical Access + Replay unit target: 369 passed
Full offline regression:               2680 passed, 83 deselected
Compileall через временный pycache:      clean
Tracked и untracked whitespace checks:   clean
Implementation read-only review:         clean
Formal acceptance read-only review:       clean

Подэтап 060.29.6 принят. На этом этапе Build 060.29 оставался In Progress.

Проверка реализации PostgreSQL Replay and Failure Verification 060.29.7:

Новый PostgreSQL Replay target:          12 passed
Десятикратный прогон target:             10 × 12 passed
Full PostgreSQL Storage integration:     77 passed
Full integration with PostgreSQL:        90 passed
Historical Access + Replay unit target: 369 passed
PostgreSQL opt-out:                      77 skipped
Pyright mandatory gate:                   0 errors, 0 warnings
Pyright pytest gate:                      1 passed
Full offline regression:               2681 passed, 95 deselected
Compileall через временный pycache:      clean
Два повторных read-only review:          clean
Production-код в 060.29.7:               только static narrowing fix;
                                         контракт и поведение неизменны

Подэтап 060.29.7 принят после чистого формального приёмочного read-only review. На этом этапе Build 060.29 оставался In Progress до выполнения 060.29.8.

Финальная приёмочная проверка 060.29.8:

Pyright mandatory gate:                   0 errors, 0 warnings
Pyright pytest gate:                      1 passed
Expanded Access/Replay/Storage unit:     545 passed
PostgreSQL Replay target repeated:       10 × 12 passed
Full PostgreSQL Storage integration:      77 passed
Full integration with PostgreSQL:         90 passed
PostgreSQL opt-out isolation:              77 skipped
Fixed stress target:                       3 passed, 1 deselected
Full offline regression:                2681 passed, 95 deselected
pip check:                                clean
Compileall через временный pycache:       clean
Tracked/untracked whitespace checks:      clean
Три независимых read-only review:         clean
Production-код в 060.29.8:               без изменений

В integration и stress наборах ResourceWarning считался ошибкой. Подэтап 060.29.8 и весь Build 060.29 приняты.


19. Следующий Build

Build 060.29 завершён и принят. Следующий этап — Build 060.30, итоговый аудит и документальное закрытие ветки Trades Feed.