- Зачем:
- зафиксировать результат кросс-прохода другой родословной по
c672ed0..ec815ce для передачи в багфикс-цепочку.
- Что:
- отчёт: 1 critical, 5 high, 10 medium, 4 low; четыре сквозных семейства
(смешение миров, стенд без live, ложнозелёные проверки, чистый клон).
- маршрутизация находок и предложение порядка багфикс-цепочки.
- Проверка:
- находки доказаны исполнением: мутационные тесты, compose-эксперимент,
зонды на сиде, Context7 по Airflow 2.10.5.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
161 lines
14 KiB
Markdown
161 lines
14 KiB
Markdown
# Кросс-линейное ревью цепочки генератора 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-теста зелёные.
|