- Зачем:
- после 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>
5.2 KiB
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, в liveFirefox / 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.