- Зачем:
- follow-up задачи после ревью 2026-06-14 лежали без триажа; решения по
артефакту, громкому отказу и интерфейсу приняты 2026-07-04 и должны
попасть в задачи до передачи исполнителю.
- Что:
- задачи 07 (миграция курса) и 08 (артефакт) поменяны местами — номера
отражают порядок; ссылки обновлены.
- 07 (артефакт + runbook) дооформлен: импорт строго через Kafka (напрямую
в ClickHouse не пишет), громкий отказ при несовместимом state с правкой
спеки, граница runbook «использование, не устройство»; ready-for-agent.
- 08 дооформлен: устройство генератора вне пути менти, реальный объём
(make data во всех уроках 00-05), демо вне скоупа; ready-for-agent.
- новые задачи: 11 глаголы/длительность/профили (после 07, до 12),
12 Airflow-DAG как пульт (приоритет поднят), 13 доливка (после 09).
- задачи 01-06 переведены в done (стояли ошибочные ready-for-human);
PRD фичи дополнен списком задач 7-13 с порядком и зависимостями.
- Проверка:
- head -1 .scratch/generator-model-time-startup-history/issues/*.md;
grep по старым именам файлов ничего не находит вне handoff.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.4 KiB
8.4 KiB
Status: done
Восстановление state v2 от модельной точки
Parent
.scratch/generator-model-time-startup-history/PRD.md
What to build
Привести восстановление state v2 к модельному времени. Один и тот же слепок
должен уметь восстанавливаться в двух разных случаях: после сбоя, где время
действительно прошло, и при старте из стартовой истории, где продолжение идёт
от T_end без искусственного разрыва.
Срез должен доказать поведение не только локальными тестами состояния, но и данными в ClickHouse: активные визиты продолжаются или закрываются по правилам модельного времени.
Acceptance criteria
- State сохраняет достаточно данных, чтобы после сбоя вычислить модельную точку возобновления при ×K.
- Восстановление после короткого модельного сбоя продолжает активные визиты и досылает созревшие события с исходными модельными метками.
- Восстановление после долгого модельного сбоя закрывает сильно просроченные активные визиты без досылки остатка.
- Восстановление из стартовой истории использует
T_endкак точку возобновления и не обрывает активные визиты из-за настенного простоя. - Визит, переживший восстановление, остаётся однородным: события до и после
восстановления не меняют контекст визита и не создают второй путь генерации
внутри одного
click_id. - Повреждённый или несовместимый state не валит сервис: генератор стартует с чистого листа и пишет предупреждение.
- ClickHouse-проверка подтверждает, что на стыке восстановления нет дублей
событий и нет разрыва
click_idвнутри продолжающегося визита. - После реализации выполнено саморевью worker-а и отдельное reviewer-ревью, потому что задача меняет state/serialization и сервисное восстановление.
- Если исправление после ревью меняет формат state или способ восстановления, выполнен повторный reviewer-круг.
Blocked by
.scratch/generator-model-time-startup-history/issues/03-model-speed-and-day-factor.md- Review gate из
PRD.md: сквозной инвариант времени после задачи 3
Решение
- State v2 теперь хранит
model_timestamp,wall_timestamp,model_time_speed,model_timezone,model_t0иgen_seed. - Старый v2 без этих полей считается несовместимым:
from_dict_safeвозвращаетNone, сервис пишет предупреждение и стартует с чистого состояния. - Live-восстановление считает точку продолжения по формуле
model_timestamp + max(0, wall_now_utc - wall_timestamp) * model_time_speed. - Перед live-восстановлением сервис сверяет state с текущим
Configпоgen_seed,model_t0,model_timezoneиmodel_time_speed. Несовпадение считается несовместимым state: сервис пишет warning и стартует с чистого состояния. - Восстановление из стартовой истории вынесено в минимальный метод
GeneratorService.restore_from_startup_history(state, model_t_end): он берёт переданныйT_endнапрямую и не применяет wall-дельту. - Сохранение state после успешного тика пишет модельную точку следующего тика, а не настенное время. Это убирает повторный старт с уже обработанной модельной точки.
Проверки
- Точечный прогон после проверки reviewer-а:
docker run --rm -v ... generator:test pytest tests/test_state.py::TestGeneratorState tests/test_state.py::TestGeneratorStateValidation::test_from_dict_safe_returns_state_on_valid tests/test_state.py::TestGeneratorStateValidation::test_from_dict_safe_returns_none_on_invalid_gen_seed tests/test_service.py::TestGeneratorServiceStateV2 -v— 15 passed. make generator-test— 126 passed.uv run pytest ...иuv run python -m pytest ...не запускались до тестов: в текущем uv-окружении нетpytest.make lintнедоступен: вMakefileнет такой цели.
ClickHouse-проверка
Выполнены два чистых стендовых сценария. Оба начинались со сброса ClickHouse, Kafka и state генератора.
- Короткое восстановление,
GEN_MODEL_TIME_SPEED=1. Первый запуск сGEN_STATE_RESET=trueзаписал один тик, второй запуск сGEN_STATE_RESET=falseвосстановился из state и продолжил активные визиты. Проверка ClickHouse:dds.event:rows=332,uniq_events=332,duplicate_events=0.continued_from_first_tick=67для визитов, начатых на2026-01-01 10:00:00.- Для этих
click_idнет сменыuser_domain_id,device_type,os_name,geo_country,geo_region_nameиgeo_timezone.
- Долгое восстановление,
GEN_MODEL_TIME_SPEED=3600. Первый запуск сGEN_STATE_RESET=trueзаписал один тик на2026-01-01 10:00:00, второй запуск сGEN_STATE_RESET=falseвосстановился на модельной точке2026-01-03T22:51:51.552000+00:00. Проверка ClickHouse:dds.event:rows=200,uniq_events=200,duplicate_events=0.- диапазон
event_ts: от2026-01-01 10:00:00.000000до2026-01-03 22:51:51.552000. continued_old_clicks=0: старыеclick_idс первого тика не продолжились после долгой модельной паузы.
Риски
- Старые записи state v2 без модельной связки больше не восстанавливаются. Это намеренно: без этих полей нельзя корректно посчитать точку продолжения.
- Полноценный манифест и backfill не реализованы: добавлен только минимальный
интерфейс восстановления от переданного
T_end. restore_from_startup_historyпока принимает state иT_endнапрямую, без проверки manifest. Это риск issue 05, где появится полноценная стартовая история и запрет смешивания артефактов.GeneratorState.__post_init__оставляет in-memory совместимость для прямого создания state в тестах и коде. JSON/Kafka путь остаётся строгим: отсутствие новых полей не маскируется и ведёт к fresh start.- После reviewer-fix формат state снова изменился (
gen_seed). Повторный reviewer-круг выполнен, новых блокирующих находок нет.