Status: needs-triage # Портативный артефакт стартовой истории и runbook по стенду ## Parent `.scratch/generator-model-time-startup-history/PRD.md` ## Why Сейчас стартовая история персистится только в Kafka compact-топиках (`generator_state`, `generator_startup_history_manifest`) и в ClickHouse. Чистая пересборка стенда (`make generated-history-analytics` делает `down -v`) каждый раз **заново генерирует** backfill. Портативного файла-артефакта, который можно сгенерировать один раз и быстро залить на чистый ClickHouse без запуска генератора, нет. Из-за этого неудобно: раздать готовое демо, мгновенно сбросить стенд, держать длинную стартовую историю (2+ суток, чтобы суточная волна повторялась на графике) без повторной генерации. Сейчас «дёшево» только live-возобновление из слепка и перезапуск без `down -v`; полный сброс требует регенерации. Этот пункт работает на главную цель: если стенд поднимается одной командой и есть короткий runbook, генератор становится скрытой инфраструктурой и менти не нужно знать его устройство. Связано с открытым вопросом курса про отдельный урок по генератору (см. `docs/course/PRD.md`, §7). ## What to build - Экспорт стартовой истории в портативный файл-артефакт: события плюс слепок состояния плюс манифест — один связный набор, чтобы не смешать `GEN_SEED`, `T0`, `T_end` и настройки генерации. - Импорт: залить артефакт на чистый ClickHouse и Kafka без прогона генерации; live-режим продолжает с `T_end`. - Runbook «как пользоваться стендом на генерации»: как сгенерировать, сохранить, восстановить, выбрать длительность стартовой истории; что дёшево (live-возобновление, перезапуск без чистки), а что требует регенерации. ## Acceptance criteria - [ ] Есть команда экспорта: backfill -> портативный файл-артефакт (события + слепок + манифест). - [ ] Есть команда импорта: артефакт -> чистый ClickHouse без запуска генератора; контрольные числа манифеста и ClickHouse совпадают с исходной генерацией. - [ ] Сохранено антисмешивание: импорт отвергает артефакт, несовместимый по манифесту (`GEN_SEED`, `T0`, `T_end`, настройки генерации, версия state). - [ ] Runbook описывает генерацию один раз, дешёвое восстановление, выбор длительности и то, что переживает перезапуск, а что требует регенерации. - [ ] Документы запуска (`README.md`, `docs/OPERATIONS.md`, `generator/README.md`) ссылаются на runbook. ## Notes - Опирается на спеку `docs/specs/2026-06-14-generator-model-time-and-startup-history.md`, разделы «Манифест стартовой истории» и «Повторяемая проверка в ClickHouse». - Спека уже упоминала будущий runbook «проверка генератора на стенде» — этот issue его и закрывает, расширяя до полного цикла «генерация — сохранение — восстановление». - Связано с `07-migrate-course-from-archive-seed.md`: удобный стенд упрощает выбор «адаптировать уроки», а не писать тяжёлый урок про генератор. ## Идеи интерфейса (на будущее, не решено) Запуск сейчас недружелюбный: поведение собирается из ~10 связанных env-переменных, `T_end` задаётся абсолютной меткой вместо длительности, а несовпадение настроек при live-продолжении даёт тихий «свежий старт» (warning в лог, общее сообщение, без указания разошедшегося поля — `service.py:217-220`). Идеи, как сделать удобнее: - **Глаголы вместо матрицы флагов:** явные `backfill` / `continue` / `reset`, а не комбинация `GEN_RUN_MODE` + `GEN_STATE_RESET`. - **Длительность как длительность и профили:** `HISTORY_DURATION=2d` вместо ручного расчёта `T_end`; именованные профили вместо повторения блока из ~10 переменных. - **Громкий и адресный отказ при несовпадении (пересмотр решения спеки).** Сейчас при `GEN_STATE_RESET=false` несовместимый по настройкам state молча ведёт к чистому старту (`service.py:217-220`) — это сознательный выбор спеки ради устойчивости. Предложение: различать два случая. Нет состояния или оно повреждено -> чистый старт с предупреждением (как сейчас, оставить). Состояние есть и читается, но настройки несовместимы при `GEN_STATE_RESET=false` (оператор намерен продолжить) -> **жёсткое падение** с указанием разошедшихся полей (`seed`/`T0`/`timezone`/`speed`) и подсказкой выставить `GEN_STATE_RESET=true`, если новый мир нужен осознанно. Меняет правило спеки «несовместимо -> чистый старт», поэтому правка идёт вместе с обновлением `docs/specs/2026-06-14-generator-model-time-and-startup-history.md`. - **Доливка прошлого кусочком:** backfill, продолжающий слепок от `T_end` (сейчас backfill всегда стартует с чистого состояния от `T0`, `service.py:178-185`). - **Airflow DAG как пульт запуска (идея пользователя, 2026-06-14):** обернуть операции генератора в параметризованный DAG (params: режим, `T0`, длительность, скорость, seed) — UI, валидация настроек против манифеста до запуска, повторные попытки, наглядность. Хорошо ложится на ограниченный backfill/доливку (конечная задача); непрерывный live — это долгоживущий сервис compose, DAG его скорее стартует/останавливает, чем держит внутри таска. Бонус: такой DAG сам по себе учебный (тема урока 4 — оркестрация Airflow), что ближе к цели курса, чем устройство генератора. ## Blocked by - `.scratch/generator-model-time-startup-history/issues/05-startup-history-backfill-to-clickhouse.md` - `.scratch/generator-model-time-startup-history/issues/06-generated-history-as-analytics-source.md`