diff --git a/.scratch/generator-model-time-startup-history/issues/20-flaky-runtime-seam-check.md b/.scratch/generator-model-time-startup-history/issues/20-flaky-runtime-seam-check.md index e370a20..df2f312 100644 --- a/.scratch/generator-model-time-startup-history/issues/20-flaky-runtime-seam-check.md +++ b/.scratch/generator-model-time-startup-history/issues/20-flaky-runtime-seam-check.md @@ -51,34 +51,72 @@ location/device/geo -> DDS строит `dds.event` через `LEFT JOIN locati Судьба гипотез: 1. Запасная ветка рождения — **опровергнута**: ветка уже хранит донора - (`generation.py:194-209`, комментарий «Запасная ветка тоже восстановима») + (`generation.py:196-209`, комментарий «Запасная ветка тоже восстановима») и громко падает на неполных locations. Перепроверено координатором по коду. 2. Недетерминизм live — **подтверждена частично**: решает не сид, а настенный момент остановки процесса и какие топики успели дописаться. 3. Проверочный SQL — **опровергнута в формулировке**: поля не «легитимно различаются», SQL маскирует пропущенный location под «смену фактуры». -## Направление фикса (из диагноза) +Уточнение механизма обрыва (адверсарное ревью постановки, 2026-07-12; +перепроверено координатором по коду): -1. **Основное:** сделать остановку live управляемой — завершать после полного - batch/tick и flush всех топиков (bounded live mode или ожидание записи - `generator_batch_history` с полными sent-счётчиками). +- Генератор не обрабатывает SIGTERM: ловится только `KeyboardInterrupt` + (`service.py:175`), а `docker compose stop` шлёт именно SIGTERM. В compose + у сервиса generator нет `init:`/`stop_signal:`, python работает PID 1 — + SIGTERM игнорируется, и через grace-период прилетает SIGKILL: жёсткий + обрыв посреди batch, без `finally` и без flush. +- Почему отставание device/geo не валит гейт, а location валит: device/geo + привязаны к `click_id` (одно значение на визит) — при пропуске весь визит + однородно NULL, массив уникальных значений длины 1, проверка проходит; + location привязан к `event_id` — частичный пропуск даёт смешанный массив + (реальное значение + NULL) и «смену фактуры». Догонять device/geo фикс + не обязан — важна граница batch. + +## Направление фикса (развилка решена после ревью постановки 2026-07-12) + +1. **Основное: корректная обработка SIGTERM в генераторе.** Минимальный + хендлер: по SIGTERM выставить `_running = False`, дать текущему tick'у + дописаться (все четыре топика + flush + запись batch history), затем + штатный `finally`/`stop()`. Runtime-check после `docker compose stop` + дожидается фактического завершения контейнера. Так генератор завершается + только на границе batch — гонка снята по построению, а не вероятностно. + - **Отклонено: «ожидание записи `generator_batch_history` перед stop» + как самостоятельный фикс** — запись history подтверждает только + ПРОШЕДШИЙ batch (`service.py:586-603`, пишется после цикла публикации); + следующий batch к моменту stop уже может быть в полёте, и SIGKILL + оборвёт его так же. Довод — ревью постановки, перепроверен по коду. + - Bounded live mode (генератор сам останавливается по лимиту) — более + тяжёлая альтернатива; в скоуп не входит, возвращаться к ней только + если SIGTERM-хендлера окажется недостаточно (с доводом в задаче). 2. **Дополнительно:** precheck в seam-check на непарные browser/location/device/geo live-строки — чтобы ошибка называла реальную - причину, а не «фактура поменялась». -3. Просто увеличить sleep перед stop — отклонено: снижает вероятность, - но гонку не убирает. + причину, а не «фактура поменялась». Precheck не подменяет основную + проверку: настоящая смена фактуры внутри `click_id` обязана падать + как и раньше. +3. **Отклонены любые вероятностные смягчения**, а не только «увеличить + sleep»: settle-sleep перед прогоном, рост `WAIT_LIVE_ROWS`/`LIVE_SECONDS`, + сужение окна `GEN_LIVE_CHECK_MINUTES` и прочие способы снизить + вероятность — гонку они не убирают и фиксом не считаются. ## Acceptance criteria - [x] Причина расхождения 8/19 найдена и названа (код, не догадка) — см. «Диагноз» выше. -- [ ] Остановка live в runtime-check управляемая: batch дописывается во все - топики целиком до остановки (направление фикса, пункт 1). +- [ ] Генератор корректно завершается по SIGTERM: текущий batch дописывается + во все четыре топика целиком (flush + запись history), потом процесс + выходит; runtime-check дожидается фактической остановки контейнера + (направление фикса, пункт 1). - [ ] Seam-check различает «непарные live-строки» и «смена фактуры»: precheck называет реальную причину (пункт 2). -- [ ] `make generated-history-runtime-check` стабилен: N подряд прогонов - зелёные (N >= 3), зафиксировано в задаче. +- [ ] Красный сценарий по-прежнему ловится: настоящая смена per-event + фактуры внутри `click_id` (инъекция в тесте или контролируемое искажение + данных) валит гейт с прежним сообщением — precheck и фикс не сделали + проверку мягче. +- [ ] Стабильность обоснована структурно (гонка снята по построению: + завершение только на границе batch), а не статистикой прогонов; + `make generated-history-runtime-check` — N подряд зелёных (N >= 3) + как дымовая проверка поверх этого довода, зафиксировано в задаче. ## Blocked by