From 749ae91a07ad42c99e34cfb8a9b80506ea0282be Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 5 Jul 2026 00:03:44 +0300 Subject: [PATCH] =?UTF-8?q?docs(handoff):=20=D0=BA=D1=80=D0=BE=D1=81=D1=81?= =?UTF-8?q?-=D0=BB=D0=B8=D0=BD=D0=B5=D0=B9=D0=BD=D0=BE=D0=B5=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20=D1=86=D0=B5=D0=BF=D0=BE=D1=87=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - зафиксировать результат кросс-прохода другой родословной по c672ed0..ec815ce для передачи в багфикс-цепочку. - Что: - отчёт: 1 critical, 5 high, 10 medium, 4 low; четыре сквозных семейства (смешение миров, стенд без live, ложнозелёные проверки, чистый клон). - маршрутизация находок и предложение порядка багфикс-цепочки. - Проверка: - находки доказаны исполнением: мутационные тесты, compose-эксперимент, зонды на сиде, Context7 по Airflow 2.10.5. Co-Authored-By: Claude Fable 5 --- ...60705-0002-cross-review-generator-chain.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 .scratch/handoffs/20260705-0002-cross-review-generator-chain.md diff --git a/.scratch/handoffs/20260705-0002-cross-review-generator-chain.md b/.scratch/handoffs/20260705-0002-cross-review-generator-chain.md new file mode 100644 index 0000000..b38c3bd --- /dev/null +++ b/.scratch/handoffs/20260705-0002-cross-review-generator-chain.md @@ -0,0 +1,160 @@ +# Кросс-линейное ревью цепочки генератора 12 -> 14 -> 09 -> 10 -> 08 + +Дата: 2026-07-05 00:02. +Жанр: одноразовый handoff по ADR-0003; отчёт кросс-прохода другой родословной +(Claude) по диапазону `c672ed0..ec815ce`. Бриф — из +`.scratch/handoffs/2026-07-04-generator-chain-review.md`. +Метод: четыре параллельных адверсарных ревьюера с исполняемыми доказательствами +(мутационные тесты на копии, эксперимент с compose-профилем на реальной версии +Compose, зонды на реальном сиде, сверка Airflow API через Context7). Репозиторий +не менялся. + +## Вердикт + +**CHANGES_REQUESTED.** Счёт: 1 critical, 5 high, 10 medium, 4 low — после +`APPROVED` финального ревью той же линии. Третье подтверждение находки о +родословной: внутренние ревью закрыли локальные дефекты честно, кросс-линия +нашла сквозные сценарии. + +## Сквозные семейства (главная ценность прохода) + +1. **«Смешение миров» возвращается тремя дорогами.** Инвариант задачи 07 + («чистый стенд, громкий отказ») пробит: (а) state v2 после обновления кода + классифицируется как «повреждённый» → тихий чистый старт нового мира поверх + истории; (б) live-генератор ушёл в compose-профиль, и `make clean`/`down` + его больше **не убивают** — осиротевший генератор старого мира пишет в чистые + топики и переживает перезагрузку (`restart: unless-stopped`); (в) консольные + пути (`make generator-backfill`, `startup-history-import`) остались без + предпроверок, которые пульту сочли обязательными. +2. **Последствия «make up больше не стартует live» не выметены.** Алерт + `Kafka No Messages Produced` горит весь курс на здоровом стенде; уроки 0/5 + ожидают поток; `sleep 5` в документированной проверке шва меньше времени + старта генератора — проверка стала пустышкой. +3. **Ложнозелёные проверки.** Superset-тесты пропускают дрейф параметров + (top-15 → 500 строк — 5 тестов зелёные); стыковая проверка сверяет шов там, + куда указал env, а не где он по манифесту; ODS-блок проходит на пустом ODS. +4. **Чистый клон/свежий стенд никто не прошёл.** `uv` не заявлен в + пререквизитах; README-быстрый старт падает без `ddl_init`; пульт вечно висит + на запаused `etl_pipeline`; после `git pull` образ Airflow не пересобирается + (DAG красный); центральное упражнение урока 1 физически невоспроизводимо. + Всё это лежало ровно в вынесенной в риски HITL-приёмке. + +## Индекс находок по issue + +### Issue 08 (курс) — CHANGES_REQUESTED + +- **CRITICAL** `docs/course/lessons/01_kafka_to_clickhouse.md:192-239` — + упражнение с `kafka_msg_ts` невоспроизводимо: «перезаливка среза» заменена на + `make generated-history-analytics && make up`, который делает `down -v` и + накатывает эталонный DDL — добавленная менти колонка уничтожается, финальный + SELECT падает; в тексте остался висящий `TRUNCATE` (след нестыковки). + Нужен путь перезаливки без сброса схемы (смена `kafka_group_name` / + `CLEAN_START=0` + повторный backfill) и проверка на стенде. +- **HIGH** `docs/course/README.md:19-34`, `README.md:43-47` — первая команда + курса требует `uv` на хосте; ни пререквизиты, ни quick start его не называют. +- **HIGH** `docs/course/lessons/05_monitoring.md:254,259-262,339` — на штатном + пути (без live) `Kafka No Messages Produced` горит постоянно; урок обещает + «алерты не шумят»; панели описаны для потока, которого нет. +- **MEDIUM** `00_kafka_intro.md:56-65` — служебные топики `generator_state` и + `generator_startup_history_manifest` не объяснены (урок построен на + разглядывании списка топиков). +- **MEDIUM** `00_kafka_intro.md:117-122` vs `05_monitoring.md:156-161` — + взаимоисключающие утверждения о видимости consumer lag; проверить на стенде. +- **MEDIUM** `README.md:64-68`, `docs/OPERATIONS.md:175-199` — + `make generated-history-check` после `PROFILE=daily-wave` падает: в check + зашит диапазон ci, PROFILE не передаётся. +- **MEDIUM** `docs/OPERATIONS.md:95` — источником STG первым назван архивный + `make data`. + +### Issue 12/14 (пульт, профиль) — CHANGES_REQUESTED + +- **HIGH** `airflow/dags/generator_control_dag.py:227-235` + + `etl_pipeline_dag.py:263` — `etl_pipeline` создаётся на паузе; `trigger_etl` + (`wait_for_completion`, без `execution_timeout`) виснет навсегда, + `max_active_runs=1` блокирует повторные запуски. В Airflow 2.10 нет + `fail_when_dag_is_paused` (Context7). Нужна предпроверка «etl_pipeline не на + паузе» + шаг unpause в README/runbook. +- **HIGH** `docker-compose.yml:293-294,334`, Makefile (`down`, `clean`) — + профильный `live-generator` не удаляется `down -v` (доказано экспериментом, + Compose v5.1.3); `restart: unless-stopped` воскрешает его после перезагрузки. + В `down`/`clean`/CLEAN_START нужен `--profile live-generator`. +- **MEDIUM** `README.md:32-40` — быстрый старт без `ddl_init`: precheck падает + сырым `UNKNOWN_TABLE`; добавить шаг и человекочитаемую подсказку. +- **MEDIUM** `airflow/requirements.txt:9-10`, Makefile `up` — после `git pull` + образ Airflow не пересобирается → `ModuleNotFoundError: prometheus_client`, + DAG красный. Нужен `--build` или инструкция. +- **MEDIUM** `docs/OPERATIONS.md:237-241` vs `kafka_io.py:161,253` — `sleep 5` + меньше времени старта continue (≥10 с): live-строк ноль, `CHECK_LIVE_SEAM=auto` + молча пропускает шов. Было `sleep 130` при тике 60 с. +- **MEDIUM** `airflow_control.py:102-141` vs `run_generator.sh:56-77`, + `import_startup_history_artifact.sh` — предпроверки только в DAG; консольные + пути тихо смешивают миры. Вынести `assert_stand_clean` в общую точку входа. +- **LOW** `service.py:203-215` — простой стенда ×60: ночь = +30 модельных суток + дыры; предупредить в runbook, советовать reset/повторный импорт. +- **LOW** — фиксация Context7-проверки Airflow API только в `.scratch/`, не в + коде/доках (AGENTS.md требует); сами вызовы для 2.10.5 корректны. + +### Issue 09 (state v3, фактура) — CHANGES_REQUESTED + +- **HIGH** `state.py:236-241` → `kafka_io.py:181` → `service.py:100-102` — + поднятие STATE_VERSION молча превращает живой стенд во «второй мир»: v2 — + читаемый, несовместимый state, но version-гейт убивает его до семантики, + `from_dict_safe` даёт `None`, сервис тихо стартует новый мир с `model_t0` + поверх истории. Сверка `manifest.state_version != state.version` + (`service.py:246`) недостижима. Спека (строки 198-204, 245-247) требует + громкий отказ; `IncompatibleStateError` уже существует — использовать его при + известной старой версии и `GEN_STATE_RESET=false`; миграционной заметки в + доках нет. +- **MEDIUM** `check_generated_analytics.sh:13,273-289,342,387-393` — проверка + стыка сверяет шов по env-дефолту (`2026-01-01T06:00`), не по манифесту: после + `daily-wave` голый `make generated-history-check` «доказывает» однородность + фальшивого шва посреди backfill. LEFT JOIN `dds.click` структурно пуст; ODS-блок + проходит на пустом ODS. Брать `model_t_end` из манифеста, требовать ненулевые + ODS-строки. +- **MEDIUM** `generation.py:196-209` — предпосылка issue «fallback не + срабатывает» неверна: срабатывает у ~8% визитов (зонд, seed=4242, N=20000); + выпуск не изменился только потому, что у всех доноров сида строки внутри + click_id идентичны — свойство нигде не зафиксировано; при регенерации сида + с вариативностью ~8% визитов поедет. Зафиксировать свойство сида или + ограничить `GEN_MAX_SESSION_EVENTS` длиной максимального донора. +- Ядро фикса крепкое (проверено исполнением): инвариант «рождение == + восстановление» держится, RNG не потребляется при restore, микросекунды + чинятся честно, форма распределений не сместилась (сверка с `e2d0684^`). + +### Issue 10 (Superset) — CHANGES_REQUESTED + +- **MEDIUM** `tests/test_superset_dashboard_config.py:64-76` — тесты сверяют + подмножество параметров: мутация «top-15 по убыванию» в экспорте (row_limit + 15→500, order_desc→false и др.) проходит все 5 тестов. Нужно полное сравнение + `params` config-vs-export. Классы «возврат world_map» и «рассинхрон layout» + закрыты честно (мутации падают). +- **MEDIUM** issue `10-*:51-68`, `create_dashboard.py:158-185` — приёмка «по + кадру» выполнена декларативно, чарт никем не отрисован; порядок столбцов после + pivot не гарантирован — задать `x_axis_sort: "Events, pcs"`, + `x_axis_sort_asc: false`; снять кадр при HITL. +- **MEDIUM** `create_dashboard.py:505-520` — удаление дублей по глобальному + совпадению имени: повторный прогон молча удалит одноимённый чарт менти; + сузить по `datasource_id`, задокументировать деструктивность. +- **LOW** `create_dashboard.py:176` — `tooltipTimeFormat` бессмыслен для + категориальной оси; легенда дублирует ось при единственной серии. +- **LOW** `docs/SUPERSET_DASHBOARD.md:205-211` — путь ручного импорта экспорта + (legacy v0) ничем не проверяется. + +## Маршрутизация (предложение) + +- **Багфикс-цепочка Codex** (кандидат-порядок по риску): (1) семейство + «смешение миров» — 09-HIGH + 12-HIGH-clean + разъезд предпроверок одним + issue; (2) 12-HIGH-паузы + README/ddl_init/build — «свежий стенд» одним + issue; (3) 08-CRITICAL урок 1 + uv + алерт урока 5 — «курс на чистом клоне»; + (4) ложнозелёные проверки (09-MEDIUM-шов, 10-MEDIUM-тесты); (5) остальное. +- **HITL остаётся HITL** (кадры Superset, живой проход Airflow UI, проход по + урокам) — но пункты 08-CRITICAL и 12-HIGH показывают, что часть HITL дешевле + заменить проверками на чистом клоне в CI-манере. +- Отдельно: третий случай подтверждения находки о родословной — кандидат на + датированную поправку в `references/rationale.md` скилла coordinator-loop. + +## Что подтверждено как корректное + +Стыковка env DAG→генератор; ветвление DAG; отсутствие потребности в Docker +изнутри Airflow; make-цели/имена DAG/DDL-колонки/числа профилей в уроках; ядро +state v3; замена карты в обоих источниках; 172 unit-теста зелёные.