- Зачем:
- после coordinator-loop нужно независимое ревью результатов модельного
времени и стартовой истории; пропущенный внешний review gate после задачи 5
закрыт другой родословной.
- Что:
- добавлен verification-handoff: что проверено независимо, дефект стыка
(issue 09) и открытые пробелы (×K, crash recovery, коридоры мат-спеки,
воспроизводимость, review gate задачи 3).
- заведены issues 08 (портативный артефакт + runbook + идеи интерфейса),
09 (баг браузерной фактуры на стыке), 10 (читаемость гео-карты).
- в docs/course/PRD.md §7 — открытый вопрос «генератор как скрытая
инфраструктура vs отдельный урок».
- Проверка:
- git show --stat HEAD
- чтение .scratch/handoffs/2026-06-14-generator-model-time-verification-review.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
5.2 KiB
Markdown
74 lines
5.2 KiB
Markdown
Status: needs-triage
|
||
|
||
# Браузерная фактура не сохраняется на стыке восстановления визита
|
||
|
||
## Parent
|
||
|
||
`.scratch/generator-model-time-startup-history/PRD.md`
|
||
|
||
## Что нашли
|
||
|
||
Визит, переживший стык backfill->live (активен на `T_end` и продолжается живьём),
|
||
меняет браузерную фактуру в live-продолжении. Внутри одного `click_id` человек как
|
||
будто «пересел» с одного браузера на другой посреди сессии.
|
||
|
||
Эмпирика. Чистый стенд, 2 суток backfill + 6 live-тиков за `T_end`,
|
||
`GEN_SEED=4242`, `GEN_MODEL_T0=2026-01-01T00:00:00+00:00`,
|
||
`GEN_MODEL_T_END=2026-01-03T00:00:00+00:00`, `speed=1`:
|
||
|
||
- `crossing_visits=22`; `browser_name` отличается у `17/22`, `browser_language`
|
||
у `22/22`; каждая сторона внутренне постоянна (`22/22`).
|
||
- Чисто-исторические визиты (>=3 событий, целиком до `T_end`): браузер постоянен
|
||
у `0/14556`.
|
||
- device / os / geo на стыке НЕ расходятся: `0/22` конфликтов на уровне ODS.
|
||
- `duplicate_events=0`: дублей на границе нет.
|
||
- Пример `click_id`: в истории `Chrome / or_IN`, в live `Firefox / el_CY`.
|
||
|
||
## Почему это дефект
|
||
|
||
Нарушает заявленный критерий задач 04 и 05: «визит, переживший восстановление,
|
||
остаётся однородным; контекст визита не меняется на стыке и не создаёт второй путь
|
||
генерации внутри одного `click_id`». Браузер — часть контекста визита.
|
||
|
||
Критерий был помечен выполненным, но проверялся неполно: саморевью и ревьюер
|
||
смотрели только поля `dds.click` (user / device / geo), которые по одной строке на
|
||
`click_id` и потому однородны структурно. Per-event браузерные поля в `dds.event` не
|
||
проверялись. Это ровно слепая зона «двух путей генерации», под которую PRD требовал
|
||
внешний review gate другой родословной после задачи 5 — а он был выполнен той же
|
||
родословной, и дефект прошёл.
|
||
|
||
## Причина (гипотеза по коду)
|
||
|
||
Конкретные браузерные строки визита (`ActiveVisit.batch`) в state не сериализуются:
|
||
хранятся `seed_click_id`, `page_url_paths`, `timestamps`, `next_index` (`state.py`).
|
||
При восстановлении live-путь пересобирает фактуру
|
||
(`runtime.py._compact_visit_batch`: `browser_by_click_id[seed_click_id]`, индекс
|
||
`event_index % len`). device и geo переживают восстановление, потому что привязаны к
|
||
`UserProfile` (`user.device`, `user.geo`) и не зависят от индекса события; браузер
|
||
зависит от выравнивания `event_index` между уже выпущенной частью и восстановленной —
|
||
вероятен рассинхрон индекса. Точную точку уточнить при фиксе.
|
||
|
||
## Масштаб и важность
|
||
|
||
- Затрагивает только визиты, активные ровно на `T_end` (здесь 22 из 17397 визитов,
|
||
~0.13%). Чистый backfill и чистый live — без дефекта.
|
||
- Тот же механизм (фактура пересобирается при restore) вероятно затрагивает и
|
||
восстановление после сбоя (задача 04), не только backfill->live; проверка задачи 04
|
||
его не ловила, потому что смотрела только `dds.click`.
|
||
- Для учебного демо урон низкий, но это реальное нарушение стыковой однородности и
|
||
сигнал, что браузерная фактура не входит в восстановимое состояние визита.
|
||
|
||
## Направление фикса
|
||
|
||
- Либо сериализовать браузерную фактуру визита в state рядом с
|
||
`page_url_paths`/`timestamps`, чтобы восстановленная часть точно продолжила
|
||
выпущенную.
|
||
- Либо выбирать браузер по абсолютному `event_index` визита, а не по позиции в
|
||
остатке, чтобы restore воспроизводил те же строки.
|
||
- В проверку стыка добавить per-event браузерные поля (`dds.event`), а не только
|
||
`dds.click`, чтобы регресс ловился автоматически.
|
||
|
||
## Blocked by
|
||
|
||
- Нет. Это bug по результатам независимой проверки задач 05 и 06.
|