From 540989d358d8d13cd82b6397cf94bbda8faabb34 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 12 Jul 2026 22:30:56 +0300 Subject: [PATCH] =?UTF-8?q?docs(generator):=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B8=2013=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=B0=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=BE=D0=B9=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - два слепых ревью постановки (Codex и Claude) вернули CHANGES_REQUIRED: guard чистого стенда противоречил доливке, не были заданы механизм идемпотентности, точка фиксации, формат цепочки границ и проверки. - Что: - добавлен раздел «Решения по ревью постановки»: next-day как новый ограниченный режим, день = [T_end, T_end+24h), предпроверка границы, параметр expected_t_end, манифест как точка фиксации, поле boundaries, батчевый режим проверки цепочки, расписание вынесено в отдельную задачу. - критерии приёмки финализированы (были черновыми), добавлен раздел «Границы (что не трогать)», исправлены ссылки на строки кода. - Проверка: - вычитка задачи; факты сверены обоими ревьюерами по коду ветки (индексы находок — в обменном каталоге сессии). Co-Authored-By: Claude Fable 5 --- .../13-backfill-top-up-from-snapshot.md | 149 +++++++++++++----- 1 file changed, 107 insertions(+), 42 deletions(-) diff --git a/.scratch/generator-model-time-startup-history/issues/13-backfill-top-up-from-snapshot.md b/.scratch/generator-model-time-startup-history/issues/13-backfill-top-up-from-snapshot.md index 09dff48..13a040a 100644 --- a/.scratch/generator-model-time-startup-history/issues/13-backfill-top-up-from-snapshot.md +++ b/.scratch/generator-model-time-startup-history/issues/13-backfill-top-up-from-snapshot.md @@ -17,67 +17,130 @@ Status: ready-for-agent модельный день -> прогон `etl_pipeline` -> витрины и дашборды сдвинулись. Полный цикл DWH виден за один шаг; повторяемо и управляемо, в отличие от непрерывного live. -- **Имитация жизни.** Тот же механизм по расписанию (`@daily` или чаще) — - стенд «живёт»: мониторинги бегут, метрики копятся, нагрузка видна. +- **Имитация жизни.** Тот же механизм по расписанию — стенд «живёт». + В этой задаче делаем глагол готовым к расписанию (идемпотентность, + громкие отказы), но само включение расписания — отдельная задача + (решение по скоупу 2026-07-12, см. «Решения по ревью постановки»). - Удлинение истории остаётся как частный случай (несколько next-day подряд). ## Интерфейс Новый глагол `next-day` в существующем DAG `generator_control` (`airflow/dags/generator_control_dag.py`), не отдельный DAG: менти уже знает, -где ручки генератора; pause-check `etl_pipeline` и guard чистого стенда -переиспользуются. Ручной триггер — для урока, расписание — для «жизни». +где ручки генератора. Pause-check `etl_pipeline` переиспользуется +(`generator_control_dag.py:153-164` — подтверждено ревью). Guard чистого +стенда **не** переиспользуется — у next-day своя предпроверка границы +(см. решение 3 ниже). ## Развилка реализации — решена (пользователь, 2026-07-07) **Выбран вариант 2: генерация следующего дня от слепка `T_end`.** Обоснование. Диагноз задачи 20 показал, что генератор на стыке ничего не -ломает — красный гейт был гонкой в самой проверке. Страх «швов» был страхом -перед артефактом верификатора. При этом оба варианта одинаково требуют -цепочку манифестов и расширение проверок, а вариант 1 сверх того требует -новый режим среза в импорте («всё или ничего» сейчас). То есть вариант 2 не -дороже, честнее (жизнь стенда не ограничена длиной артефакта) и переиспользует -уже закалённый restore-механизм (задача 09: донор в state, громкие ошибки, +ломает — красный гейт был гонкой в самой проверке. Оба варианта одинаково +требуют цепочку границ и расширение проверок, а вариант 1 сверх того требует +новый режим среза в импорте («всё или ничего» сейчас). Вариант 2 не дороже, +честнее (жизнь стенда не ограничена длиной артефакта) и переиспользует +закалённый restore-механизм (задача 09: донор в state, громкие ошибки, детерминированная запасная ветка). Отклонено: вариант 1 (резать готовый артефакт на дневные порции) — не даёт неограниченной жизни и требует своей новой механики импорта. -## Открытые вопросы (блокируют ready-for-agent) +## Решения по ревью постановки (2026-07-12, два слепых ревью) -- [x] Умеет ли текущий импорт артефакта (`startup-history-import`) подавать - историю частями? **Нет, «всё или ничего»** (проверено 2026-07-07): - скрипт требует чистый стенд (`import_startup_history_artifact.sh:50`, - `assert_stand_clean.sh`), CLI артефакта воспроизводит все топики целиком и - не имеет опций среза, а валидация жёстко связывает manifest и state - (`startup_history_artifact.py`, `_validate_manifest_state`). Для варианта 1 - нужен новый режим импорта: срез по модельному дню, цепочка manifest'ов - вместо «manifest равен state», и ослабление guard'а чистого стенда для - последующих порций (граница по манифесту вместо пустоты). Нюанс: артефакт - хранит один state на конец полной истории, поэтому live-продолжение - возможно только после последней порции; промежуточные дни — batch-only - (для учебного сценария этого достаточно). -- [x] Для варианта 2 — решение по запасной ветке рождения визита: **уже - закрыто фиксом задачи 09** (проверено по коду 2026-07-07 при диагнозе - задачи 20): ветка хранит донора (`generation.py:194-209`) и громко падает - на неполных locations, восстановление воспроизводит её детерминированно. +Оба ревью (Codex и Claude, свежие сессии) вернули CHANGES_REQUIRED. +Все находки внесены решениями ниже; полные индексы — в обменном каталоге +сессии (в репо не хранятся). -## Acceptance criteria (черновые) +1. **next-day — новый ограниченный режим запуска, а не переиспользование + существующего глагола.** Сейчас `backfill` — свежая генерация от `T0` + с чистым завершением, `continue` — бесконечный live + (`generator/src/clickstream_generator/launch.py:96-128`, + `service.py:96-128`). next-day = восстановление мира из state + + генерация ровно одного дня с самозавершением + сдвиг манифеста. + Это основная работа задачи, «переиспользуется» только restore-механизм. +2. **Один модельный день = ровно 24 часа, полуоткрытый диапазон + `[T_end, T_end + 24h)` в модельном времени (UTC).** Календарные сутки и + `GEN_MODEL_TIMEZONE` в границах не участвуют; литералы времени в + проверках — по образцу `clickhouse_datetime_literal` (урок задачи 20). +3. **Предпроверка границы вместо guard'а чистого стенда.** + `assert_stand_clean` в DAG требует пустые Kafka и STG + (`airflow/dags/lib/airflow_control.py:99-109`) — next-day по построению + доливает в непустой стенд. Своя предпроверка: манифест существует, + state согласован с `T_end` манифеста, live-генератор остановлен. + Образец «непустого» режима — `assert_stand_clean.sh continue` + (в Python-версии guard'а такого режима нет — добавить именно для + next-day, `clean`-режим не менять). +4. **Идемпотентность — через явный параметр.** У DAG-запуска next-day есть + необязательный параметр `expected_t_end`: если задан и не совпадает с + `T_end` манифеста — громкий отказ, в тексте обе границы (ожидаемая и + фактическая). Повторный триггер «того же дня» с прежним `expected_t_end` + после успешного прогона ловится этим же сравнением. Без параметра — + генерация от текущей границы (режим «жизни»); от двойного параллельного + запуска защищает `max_active_runs=1`. +5. **Точка фиксации — публикация манифеста, автоотката нет.** Порядок + публикации дня: топики данных -> state -> манифест последним. Сбой до + манифеста оставляет «хвост» за границей — его ловит проверка цепочки + (непарные счётчики), восстановление — переимпорт артефакта + (задокументировать в OPERATIONS.md). Отклонено: инкрементальный откат + через `rollback_import_topics` — он удаляет топики целиком + (`startup_history_artifact.py:250-270`) и для доливки непригоден. +6. **Цепочка границ живёт внутри единственной записи манифеста.** + Манифест остаётся одной записью compact-топика с ключом `default` + (`kafka_io.py:205-274`); добавляется необязательное поле `boundaries` — + накопительный список границ `[T0, d1, ..., T_end]`. Старый манифест без + поля читается как `[T0, T_end]`. Итоговые счётчики и контрольные суммы + остаются накопительными за всю историю. Формат артефакта меняется только + этим необязательным полем — без новой версии и без изменения импорта. +7. **Проверка цепочки — новый батчевый режим, live-гейт не трогать.** + Текущий seam-гейт (`scripts/check_generated_analytics.sh:334-531`) + привязан к одной границе и живому генератору — литерально «расширить» + его нельзя. Новый режим (отдельная make-цель): по каждой границе из + `boundaries` — непарные счётчики и однородность per-event полей у + визитов, переживших границу (класс проверки задачи 09). Существующий + сценарий `make generated-history-runtime-check` остаётся зелёным как был. +8. **Расписание — вне этой задачи.** `schedule=None` у DAG не меняется; + задача гарантирует только готовность глагола к расписанию (пункты 4–5). + Включение «жизни» по расписанию — отдельная задача-продолжение (в ней же + решить: как расписание задаёт `operation=next-day`, ведь параметр DAG по + умолчанию — `backfill`, и планового запуска с дефолтом быть не должно). +9. **Исправлены ссылки:** запасная ветка рождения визита — + `generation.py:196-209` (строка 194 — основная ветка); донор + сериализуется в `runtime.py:178-226`. -- [ ] Глагол `next-day` в `generator_control`: батч ровно за один следующий - модельный день от текущей границы манифеста. -- [ ] Идемпотентность: повторный триггер того же дня — громкий отказ по - манифесту, а не второй мир и не дубли. -- [ ] Манифест двигает `T_end` атомарно; антисмешивание работает на **цепочке** - границ (30 батчей = 30 границ), проверки - (`scripts/check_generated_analytics.sh` и родня) расширены с одной границы - на цепочку. -- [ ] Учебный цикл целиком: после `next-day` прогон `etl_pipeline` даёт новые - строки в DM-витринах; проверка повторяемая, не разовая. -- [ ] Для варианта 2: визиты, пережившие границу батча, однородны по всем - per-event полям (браузер, referer, utm) — тот же класс проверки, что в - задаче 09. +## Acceptance criteria + +- [ ] Глагол `next-day` в `generator_control`: батч ровно за + `[T_end, T_end + 24h)` от `T_end` манифеста; после прогона манифест + содержит новую границу в `boundaries` и новый `T_end`. +- [ ] Предпроверка границы: на пустом стенде (нет манифеста) next-day + громко падает; при работающем live-генераторе — громко падает; + `clean`-guard и существующие глаголы `backfill|import|check` не изменены. +- [ ] Идемпотентность: запуск с `expected_t_end`, не равным `T_end` + манифеста, — громкий отказ с обеими границами в тексте; повторный запуск + того же дня после успеха — тот же отказ, дублей и второго мира нет. +- [ ] Проверка цепочки (новая make-цель): проходит по всем границам из + `boundaries`; после N прогонов next-day в цепочке N новых границ; + непарные счётчики нулевые, per-event поля визитов через каждую границу + однородны (браузер, referer, utm). +- [ ] Учебный цикл целиком и повторяемо: next-day -> прогон `etl_pipeline` + -> в DM-витринах появились строки ровно за новый день (счётчики до/после, + прирост только в диапазоне нового дня); проверено на двух next-day подряд. +- [ ] Регрессий нет: тесты генератора и контракты корня зелёные + (`make test`), сценарий `make generated-history-runtime-check` + задачи 20 остаётся зелёным. +- [ ] Документация: OPERATIONS.md — глагол, предпроверка, поведение при + сбое до фиксации манифеста (восстановление переимпортом артефакта). + +## Границы (что не трогать) + +- Поведение существующих глаголов `backfill`, `import`, `check` и + `clean`-guard'а. +- Формат артефакта — кроме необязательного поля `boundaries` (решение 6); + механику импорта не менять. +- Live seam-гейт задачи 20 (`generated-history-runtime-check`) — не + редактировать, только прогонять как регрессию. ## Blocked by @@ -92,4 +155,6 @@ Status: ready-for-agent рождается при любом прохождении мира через сериализованный state (рестарт, сбой, доливка — один код восстановления); фикс 09 положил донора фактуры в state и закрыл тихие fallback'и. Прежняя редакция этой задачи называлась -«Доливка стартовой истории кусочком от слепка». +«Доливка стартовой истории кусочком от слепка». Проверенные 2026-07-07 +факты (import «всё или ничего», донор задачи 09) подтверждены обоими ревью +2026-07-12 по коду ветки.