Commit Graph
38 Commits
Author SHA1 Message Date
ddadminandClaude Fable 5 c28f724706 feat(generator): потеря и дубль purchase (B, D) и счетчики описи
- Зачем:
  - трекер стенда был полным, и правило «деньги — по бэкенду» держалось
    на слове; классы B и D делают недосчет и пересчет трекера
    наблюдаемыми (этап 6, #184).
- Что:
  - подпоток BEACONS: точечная потеря purchase (~3%) и сюжетный дубль
    (~2%) швом после _purchases, с гарантией моста идентичностей и
    условием допуска дубля по паузе до следующего визита;
  - опись считает пять классов по приоритету спеки; фильтр
    classes_vs_inventory расширен, нулевой класс читается нулем;
  - fate.md, seeds.md, dm.md и orders/README.md приведены к имени
    BEACONS и строгому правилу допуска.
- Проверка:
  - make lint, make typecheck, make test (433 passed) и make inventory
    из generator/ — хеши слепков не изменились;
  - на стенде: make smoke, make check-clickhouse, make check-services
    зеленые; в dq_summary classes_vs_inventory и sessions_vs_reference
    pass, витрина сверки показывает lost_event и duplicate_event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 13:13:47 -04:00
ddadminandClaude Fable 5 17995d4c5e fix(airflow): формулировки пульта переписаны живым русским
- Зачем:
  - владелец на ревью PR #175 отметил кальку в подсказках; читать их
    будет менти, и язык здесь — часть продукта.
- Что:
  - переписаны подсказка формы days, докстринги работников, строки
    журнала remember_played, абзац README и пункт в etl.md; смысл
    не менялся.
- Проверка:
  - make lint, make config-test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 05:14:51 -04:00
ddadminandClaude Fable 5 c2c4401a16 feat(airflow): пульт мира объясняет ось дней в форме и журнале
- Зачем:
  - оператор принимал служебную logical_date Airflow за дату мира (#111),
    а документацию читают реже, чем форму запуска.
- Что:
  - у параметра days появилась подсказка description_md, а в докстринги
    работников добавлена фраза: день определяет world_position, служебные
    поля Airflow к оси мира отношения не имеют.
  - remember_played пишет в журнал сыгранные дни и новую позицию.
  - README дополнен абзацем про служебные поля за «Advanced Options»;
    из etl.md сняты устаревшие стыки на закрытые #7 и #2.
- Проверка:
  - make lint, make config-test; ручной прогон world_next_day зелёный,
    позиция 9 -> 10, в журнале «сыграны дни 9–9, позиция теперь 10».

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 05:05:44 -04:00
ddadminandClaude Fable 5 f33d75113d feat(dm): добавлены учебные задания витрин и даг самопроверки
- Зачем:
  - менти строит витрины DM сам, от DDL по правилу зерна до задачи в
    конвейере; стенду нужны ТЗ с образцом и обратная связь по каждой
    витрине (#159, резолюция #135).
- Что:
  - docs/labs/: README и пять ТЗ — events_enriched_v, top_pages_daily,
    session_overview, dq_errors_daily, utm_effectiveness; каждое ТЗ
    называет урок, состав колонок, форму по зерну и самопроверку.
  - dags/homework_check.py: ручной даг, задача на витрину, красная без
    витрины или при расхождении; проверки — шесть файлов sql/dq/ с
    инвариантами против DDS и ODS, без золотых констант; в dq_summary
    не пишет.
  - docs: dm.md называет homework_check и снимает «Остается нарезке»,
    §7 и §10 мастер-спеки согласованы с лабами, dds/session.md указывает
    на ТЗ, AGENTS.md пополнен правилом имен docs/labs/.
- Проверка:
  - make lint; make smoke 21/21, make check-clickhouse 10/10,
    make check-services 7/7.
  - на свежем стенде все пять задач homework_check красные и называют
    ТЗ; черновик каждой витрины по тексту ТЗ зеленит свою задачу,
    после сноса — снова красная; строк от самопроверки в dq_summary нет.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:44:15 -04:00
ddadmin 54686651df fix(dq): устранено переполнение памяти проверок
- Зачем:
  - параллельный DQ выгружал деловые ключи в Python и исчерпывал 640 МиБ планировщика.
- Что:
  - четыре сверки агрегируют дневные итоги и ограниченную диагностику в ClickHouse.
  - коробка планировщика поднята до 1,5 ГиБ, локальные пределы задач удалены.
  - измерения и граница между SQL и Python записаны в документации.
- Проверка:
  - make lint, make config-test, make smoke, make check-clickhouse, make check-services.
  - полный etl_pipeline, красный и восстановительный прогоны dq_check.
2026-08-27 11:25:19 -04:00
ddadmin 26b2e5fea1 feat(dq): добавлен контроль качества конвейера
- Зачем:
  - конвейеру нужна публикуемая сводка текущего качества с красным завершением при нарушении договоров.
- Что:
  - добавлены DDL, публичное представление и пять сверок на естественном деловом зерне.
  - даг dq_check встроен после DM и публикует полные дневные партиции до именованных утверждений.
  - параллелизм DDS, DM и DQ приведён к измеренному лимиту памяти планировщика.
- Проверка:
  - make lint; make config-test; make rebuild-storage.
  - make smoke; make check-clickhouse; make check-services.
  - три семейства проверок покрашены искусственными расхождениями и восстановлены повторным полным прогоном.
2026-08-27 07:55:28 -04:00
ddadmin cec11edaf3 chore(repo): влиты изменения задачи 155
- Зачем:
  - ветка ETL должна учитывать новую витрину `purchase_vs_orders` из `main`.
- Что:
  - `origin/main` влит без переписывания истории опубликованного PR;
  - `dm_transform` теперь собирает третью витрину внутри общего конвейера.
- Проверка:
  - `make config-test`, `make lint`;
  - `generator`: `make lint`, `make typecheck`, `make test` — 429 тестов;
  - `make rebuild-storage`, `make smoke`, `make check-clickhouse`, `make check-services`;
  - импорт DAG без ошибок, `purchase_vs_orders` заполнена.
2026-08-27 12:23:02 +03:00
ddadmin 20071c565f feat(dm): добавлена сверка покупок с заказами
- Зачем:
  - показать расхождения трекера и бэкенда на зерне заказа
  - сохранить отложенные покупки наблюдаемыми до следующего слепка
- Что:
  - добавлены таблицы, представление и пересборка purchase_vs_orders
  - сумма покупки нормализована в Decimal на границе ODS
  - обновлены договоры и указатели на механику пересборки
- Проверка:
  - `make config-test`, `make lint`
  - `cd generator && make docs lint typecheck test`
  - `make rebuild-storage`, явные `dds_transform` и `dm_transform`
  - `make smoke`, `make check-clickhouse`, `make check-services`
2026-08-27 04:21:15 -04:00
ddadmin 61e662c19c refactor(etl): упрощена проверка готовности Kafka
- Зачем:
  - верхние смещения можно получить тем же административным клиентом без одноразового Consumer и фиктивной группы.
- Что:
  - чтение хвоста переведено на AdminClient.list_offsets с OffsetSpec.latest.
  - назначение system.one названо рядом с запросом, а документация и запись проверок сокращены и переставлены по смыслу.
- Проверка:
  - make config-test; make lint.
  - живые проверки готового и отсутствующего разделов Kafka; полный etl_pipeline.
2026-08-27 11:17:43 +03:00
ddadmin 12a5c109b2 feat(etl): добавлен дневной конвейер трансформаций
Зачем:
- Один запуск должен одинаково работать из жизненного цикла, вручную после разгона и повторно после отказа.

Что:
- Добавлена последовательная сцепка DDS и DM с ожиданием каждого слоя.
- DDS дожидается смещений Kafka и очереди доставки ClickHouse за общий срок.
- ETL встроен в создание мира и живой день; документация приведена к новому графу.

Проверка:
- make config-test; make lint.
- make smoke: 21/21; make check-clickhouse: 10/10; make check-services: 7/7.
- make rebuild-storage, живой и пакетный дни, искусственный отказ и ручное восстановление ETL.
2026-08-27 02:43:29 +03:00
ddadmin 741820789f refactor(dm): уточнены ограничения DAG и пояснения SQL
- Зачем:
  - Ревью выявило неясные причины ограничений и защитных условий в учебном образце.
- Что:
  - Спекулятивный общий потолок задач заменен адресным лимитом размноженной замены с точным описанием OOM.
  - Объяснены нулевое значение непарного LEFT JOIN и опасность пустого DDS при замене партиций.
  - Добавлена ссылка на разбор области пересборки, а общий штамп про Context7 удален.
- Проверка:
  - make lint, make config-test, make smoke; успешный запуск dm_transform.
2026-08-26 14:56:33 -04:00
ddadmin da0477f0f1 feat(dm): добавлены витрины выручки и трафика
Зачем:
- Показать два ритма пересборки агрегатов на готовых витринах DM.

Что:
- Добавлены физические модели и публичные договоры revenue_daily и daily_traffic.
- Добавлен dm_transform с оконной заменой выручки и полным пересчетом трафика.
- Отрицательный опыт локального соединения через публичные _v зафиксирован; выбран явный GLOBAL LEFT JOIN.
- Замены партиций сериализованы после подтвержденного OOM при трех одновременных задачах.
- Обновлены архитектурные документы и мастер-спека.

Проверка:
- make lint, make config-test, make smoke, make check-clickhouse, make check-services.
- Чистая сборка world_recreate, dds_transform и dm_transform; меры сверены с прямыми агрегатами.
2026-08-26 13:55:26 -04:00
ddadmin 39ff0b5bb4 feat(dds): добавлена карта идентичностей
- Зачем:
  - реализованы требования issue #153 к мосту между кукой и пользователем.
- Что:
  - добавлены физическая карта, точное представление и полный пересчёт через GLOBAL JOIN.
  - задача identity_map поставлена после сборки заказов в dds_transform.
  - устройство и живые проверки карты записаны в документации хранилища.
- Проверка:
  - make lint, make smoke, make check-clickhouse, make check-services.
  - три запуска dds_transform: 4770 физических строк и 1590 уникальных пар в _v.
2026-08-26 12:30:40 -04:00
ddadminandClaude Opus 5 a82c75705d feat(ods): версией заказа стала дата слепка, окно DDS — из данных
- Зачем:
  - ярлык snapshot_date выжившей строки доставался жребием, а объём
    пересборки DDS держался на константе окна с двумя несверяемыми копиями
    (ADR 0014, развилка #163).
- Что:
  - ods.order_rep переведена на ReplacingMergeTree(snapshot_date): выживает
    строка позднейшего слепка, ярлык стал правдивым.
  - order_scope.sql выводит окно из дней последнего слепка; ORDER_WINDOW_DAYS
    и параметр window_days сняты со стенда, мёртвая защита краем источника
    убрана.
  - документы и комментарии SQL приведены к ADR 0014: роль ODS, условие
    «одна дата — один слепок», объём прогона и слепые пятна конвейера.
- Проверка:
  - make lint; make rebuild-storage, затем make smoke, make check-clickhouse,
    make check-services — зелёные.
  - после двух слепков snapshot_date всех 1917 строк ods.order_v равен
    последнему слепку заказа, расхождений ноль; деловые поля построчно
    совпали с эталоном argMax по updated_at из сырья.
  - dds_transform без K: первый прогон 06-01…06-07, после нового дня мира
    06-02…06-08, повтор прогона содержимого не изменил.

Closes #165

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 17:28:14 +03:00
ddadminandClaude Opus 5 176cf5520d feat(dds): добавлена модель сессии и нарезка визитов окнами
Зачем:
- Дать слою расчётную сущность, которой в потоке нет: стенд режет визиты сам,
  а VisitID генератора остаётся эталоном самопроверки.
- Отделить прожитый день от живого — иначе визит, начатый в 23:50, замёрз бы
  обрезанным навсегда: пересобирать замёрзший день слой не станет.

Что:
- Добавлены dds.session_rep/_dist, двойник-донор dds.session_stage_rep/_dist и
  публичный договор dds.session_v.
- Нарезка стоит на двух окнах: первое ставит шов там, где пауза длиннее
  тридцати минут, второе считает нарастающий итог швов — номер визита. Границу
  суток держит разбиение окна, отдельной проверки ей не нужно.
- Порядок в окнах полный, по метке времени и event_id: сортировок две, и при
  равных метках они разошлись бы, разорвав сессию надвое.
- Объём прогона считается от первой дыры в слое по последний прожитый день;
  прожитый день выводится из позиции мира и первого дня событий. Собирать
  нечего — группа задач пропускается, а не зеленеет впустую.
- В dds_transform появилась группа session рядом с заказом; зависимости по
  данным между ними нет.
- Потолок max_active_tasks: размноженная замена без него выносит планировщика
  за отведённые ему 640 МиБ.
- parallel_distributed_insert_select у сессий не назначается вслух — ключ
  шардирования у источника и цели один по контракту ко-локации.
- Записано, что позиция отвечает за «прожит», а не за «доехал»: сторож хвоста
  потока приезжает отдельным тикетом.

Проверка:
- make lint
- make smoke (21 проверка), make check-clickhouse (10), make check-services
- Стартовый мир, пересобранный с нуля: 79367 сессий за восемь дней;
  расхождение с uniqExact(VisitID) по паре «кука × день» — ноль; все 401185
  событий ровно в одной сессии; session_id уникальны.
- Ни одной паузы длиннее тридцати минут внутри сессии и ни одной пары соседних
  визитов куки, разделённой меньшим промежутком.
- Повтор после DROP PARTITION ALL: те же session_id и то же содержимое строк.
- Снесённая руками партиция 2026-06-04: следующий прогон взял хвост от неё,
  дни до дыры не тронуты.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 07:56:44 -04:00
ddadminandClaude Opus 5 535b03783a docs(dds): убраны повторы объяснений в комментариях заказа
Зачем:
- Полный разбор урока про идемпотентность стоял дважды, предупреждение про
  окно K — трижды; внимание менти конечно, и каждый пересказ списывается
  с этого счёта.

Что:
- Докстринг dds_transform сокращён до одной фразы с указателями: полный разбор
  остался в шапке DDL, механика подмены — в order_replace.sql.
- Комментарий про ORDER_WINDOW_DAYS в даге сведён к указателю на compose,
  где число объявлено и где записан риск расхождения.
- Прорежен повтор одного риторического приёма «это не X» — оставлен там,
  где различие и есть суть.

Проверка:
- make lint, make config-test
- Прогон dds_transform зелёный, слой сходится с ods.order_v: 1917 заказов,
  8 дней.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 05:32:03 -04:00
ddadminandClaude Opus 5 1e8b83ffbb feat(dds): добавлена модель заказа и пересборка окна заменой партиций
Зачем:
- Дать слою первую физическую сущность и показать идемпотентность, устроенную
  иначе, чем в приёме: заменой партиции, а не колонкой версии.
- Родить механику дневной пересборки на кластере — образец для сессий и витрин.

Что:
- Добавлены dds.order_rep/_dist, двойник-донор dds.order_stage_rep/_dist и
  публичный договор dds.order_v с разбором позиций в три массива.
- Добавлены запросы объёма прогона, сборки в двойник и замены партиции по дню.
- Добавлен даг dds_transform: группа задач order, замена — размноженной задачей.
- Окно изменяемости K пришло на сторону стенда якорем compose; владелец числа
  остаётся у генератора.
- Объём считается от позднейшего дня заказа и первого недостающего дня: ярлыки
  доставки ODS для этого непригодны, а максимум собранного дня прячет дыры.
- Записано, чего конвейер о себе не знает, и адресовано контуру DQ.

Проверка:
- make lint
- make smoke, make check-clickhouse, make check-services
- Прогон по стартовому миру: 1917 заказов, счёт равен ods.order_v, каждый заказ
  на шарде своего cityHash64(order_id), сумма позиций равна items_total.
- Повтор без нового слепка: содержимое то же, _load_id новый; после слепка
  пересобрано окно, день за окном не тронут.
- Снесённая руками партиция 2026-06-01 восстановлена побайтово.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 05:20:39 -04:00
ddadmin 8d82679bc1 feat(airflow): подключён официальный провайдер ClickHouse
- Зачем:
  - нужен единый штатный путь исполнения SQL и программных запросов ClickHouse из Airflow.
- Что:
  - добавлен официальный провайдер и подключения типа `clickhouse`.
  - SQL-файлы переданы `SQLExecuteQueryOperator`, а программные запросы — `ClickHouseHook`.
  - разбор заказов собран в одном файле, DDL разделён по задачам, документация и смоук обновлены.
- Проверка:
  - выполнены `make lint` и `make config-test`.
  - собран образ, выполнены `pip check`, `make rebuild-storage`, `make smoke` (21/21), `make check-clickhouse` (10/10) и `make check-services` (7/7).
2026-08-22 01:40:56 -04:00
ddadmin bca1ffa379 fix(world): устранены расхождения после переноса на main
- Зачем:
  - свежий main добавил мониторинг и проявил устаревшие доводы, мёртвые настройки и слишком короткий тайм-аут DDL.
- Что:
  - документация приведена к текущему жизненному циклу мира и составу стенда;
  - удалены лишняя проверка и неиспользуемый YAML-якорь;
  - ожидание ClickHouse возвращено к 300 секундам, а отказы и параметры DockerOperator пояснены.
- Проверка:
  - `make config-test`;
  - `make lint`;
  - `make smoke` — 21/21.
2026-08-21 23:38:45 +03:00
ddadmin fc5244cc67 feat(world): жизненный цикл передан Airflow
- Зачем:
  - Compose должен поднимать инфраструктуру, а Airflow — владеть прикладным миром.
- Что:
  - добавлены даги создания и пересоздания мира с явными состояниями позиции;
  - цели make переведены на REST API Airflow, старые одноразовые службы удалены;
  - добавлена ограниченная учётная запись ClickHouse для прикладного DDL.
- Проверка:
  - `make lint`, `make config-test`, `bash -n scripts/run-dag.sh infra/airflow/init.sh`;
  - `make lint` и `make typecheck` в `generator/`;
  - холодный `make up`, повторный `make up`, `make rebuild-storage` и сценарий оборванной очистки;
  - `make smoke`, `make check-clickhouse`, `make check-services`.
2026-08-21 23:01:11 +03:00
ddadmin 2a94ee9bde refactor(ods): упрощены имена таблиц заказов
- Зачем:
  - ODS должен показывать сущность заказа, а не способ её доставки слепком.
- Что:
  - физические таблицы и представление переведены на имена `order_*`.
  - SQL загрузки, DAG и документация приведены к тому же словарю.
- Проверка:
  - `make config-test`, `make lint`.
  - `make clean && make up`, `make smoke`, `make check-clickhouse`, `make check-services`.
2026-08-21 10:49:02 +03:00
ddadmin a5508bd9ec refactor(orders): SQL приёма вынесен из DAG
- Зачем:
  - преобразования хранилища должны читаться рядом с целевым слоем, а DAG должен показывать оркестрацию.
- Что:
  - запросы забора и разбора заказов разложены по каталогам STG и ODS.
  - общий контракт провода подключён в обе ветви штатным шаблонизатором Airflow.
  - SQL смонтирован во все службы Airflow, а правило раскладки записано в архитектуре.
- Проверка:
  - make lint config-test smoke check-services.
  - airflow tasks render для pull_batch и parse_batch; airflow tasks test для parse_batch.
2026-08-18 23:55:04 +03:00
ddadmin bdcbe48b59 feat(orders): добавлен версионный ODS заказов
- Зачем:
  - менти должен различать версию заказа, наблюдение источника и запуск загрузки.
- Что:
  - добавлены таблицы версий и брака заказов, а также представление ods.order_v.
  - даг orders_ingest дополнен строгим переходом одного среза STG в две цели ODS.
  - результаты опытов записаны в документации, дефект генератора вынесен в #102.
- Проверка:
  - make lint config-test smoke check-clickhouse check-services.
2026-08-18 23:10:01 +03:00
ddadmin 6bd9190142 feat(orders): добавить приём слепков в STG
Зачем:
- связать проигрывание модельных дней с пакетным приёмом заказов
- показать на одном стенде различие потокового push и пакетного pull

Что:
- добавлен топик, Kafka-чтец и реплицированное сырьё заказов
- добавлен даг orders_ingest с одним прямым чтением и идентификатором загрузки
- работники мира отправляют слепок, ждут приём и только затем двигают позицию
- решения, границы отказа и проверки отражены в ADR и архитектурных документах

Проверка:
- make lint
- make config-test
- make smoke
- make check-clickhouse
- make check-services
2026-08-18 21:33:13 +03:00
ddadminandClaude Opus 5 292302e161 fix(airflow): правки пульта мира по двум линиям ревью
Зачем: линия дефектов нашла опору на дефект провайдера, линия постановки —
незаписанный ответ на вопрос ADR 0009 и переменную образца, чьё значение на
чужой машине неверно, а узнаёт об этом читатель через двести строк.

Что: `auto_remove` у прогона генератора переведён с `success` на `force`.
Значение `success` тоже убирало контейнер в любом исходе, но случайно —
удаление у провайдера 4.5.7 стоит в `finally` вопреки собственной
документации; `force` то же поведение называет прямо и переживёт починку.
У выключателя назван `catchup=False`: умолчание Airflow 3 то же самое, но
у дага с тиком в 25 минут и `start_date` в январе это первый вопрос
читателя. «Быстрый старт» предупреждает про `DOCKER_GID` — единственное
значение образца, неверное вне этой машины. В образе Airflow записано, что
провайдер docker приходит с базой (4.5.7 к 3.3.0), — это ответ на вопрос,
который ADR 0009 оставил тикету. Список томов планировщика получил ту же
пометку «правя одно, правьте второе», что стоит у числа дней. Проход на
вычитание срезал три комментария, пересказывавших ADR.

Проверка: `make lint`, `make config-test`, `make smoke` (20/0),
`make check-clickhouse` (9/9) — зелёные. Живой пульт на чистом стенде:
снятый с паузы `world_live` сыграл два дня подряд без нажатия (24 м 07 с и
24 м 06 с, пауза между ними 55 секунд), дочерний прогон виден ссылкой из
задачи выключателя; пауза остановила мир на границе суток — тик прошёл,
третьего прогона нет. Контейнер после прогона с `force` не остался.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:57:59 +03:00
ddadminandClaude Opus 5 35dc933d29 feat(airflow): пульт мира — работники, выключатель, контейнер
Зачем: модельный день прогонялся только руками, и владельцу нечем было
проверять процессы стенда вживую. Пульт нужен раньше этапа 3 и независимо
от него: он обкатывает то, что приёму заказов понадобится готовым — вызов
генератора из задачи Airflow.

Что: `dags/world_control.py` — три дага по ADR 0009. Работники
`world_next_day` (день пачкой, «сколько дней» параметром) и `world_live_day`
(день в темпе) живут без расписания и без паузы; выключатель `world_live`
создаётся на паузе, тикает раз в 25 минут и дёргает работника живого дня
с ожиданием конца. Генератор зовётся `DockerOperator` в каноническом
контейнере: сокет докера отдан планировщику, потому что при LocalExecutor
задачи исполняет он, а GID группы `docker` уехал в `.env` как локальная
настройка. Позицию на оси ведёт переменная `world_position` — её ставит
сыгравший день работник и только по успеху. Факты стенда — образ, сеть,
брокер, топик, размер стартового мира — даги получают окружением от compose;
внутри compose они названы по разу якорями, иначе разошлись бы с разовой
службой генератора. README получил раздел про пульт с названной вслух платой
за сокет.

Проверка: `make lint`, `make config-test`, `make smoke` (20/0), `make
check-services` (7/0), `make check-clickhouse` (9/9) — зелёные. На чистом
стенде: два прогона `world_next_day` подряд двигают позицию на два дня,
«дней = 3» — на три, все пять дней доехали в ODS; обрыв контейнера позицию
не двигает, повторный запуск играет тот же день с тем же счётом событий.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:09:14 +03:00
ddadmin d0f15bea02 fix(clickhouse): устранены замечания ревью модели доступа
- Зачем:
  - документация и конфигурация доступа должны говорить только подтверждённое.
- Что:
  - ADR приведён к результату межшардового замера.
  - объяснены служебный грант и учебный компромисс remote().
  - удалены избыточные профили, README связан с ADR.
- Проверка:
  - make config-test, make lint, make smoke, make check-clickhouse, make check-services.
2026-08-13 11:31:01 +03:00
ddadmin 5627f6c7aa docs(clickhouse): описана учебная модель доступа
- Зачем:
  - читателю нужна честная граница между учебными ролями и боевой защитой.
- Что:
  - README описывает пользователей, роли и место хранения паролей.
  - мастер-спека явно откладывает эксплуатационные меры защиты.
  - устаревшие комментарии Compose и пробника приведены к реализации.
- Проверка:
  - make config-test, make lint.
2026-08-13 10:26:35 +03:00
ddadmin 62dc618252 feat(clickhouse): приложения переключены на роли
- Зачем:
  - DDL, Airflow и Superset не должны работать с правами default.
- Что:
  - DDL и Airflow переведены на etl, включая явный доступ remote().
  - Superset переведён на bi с паролем из окружения.
  - etl получил право KAFKA, которое ClickHouse 26.3 требует для движка.
- Проверка:
  - make config-test, make lint, make up, make smoke, make check-services.
2026-08-13 10:19:38 +03:00
ddadminandClaude Opus 5 c9b11224d2 refactor(ruff): линтер стенда говорит на языке дагов, а не младшего образа
- Зачем:
  - target-version по младшему образу (Superset, 3.10) занижал проверку для дагов: они бегут на 3.13, а ruff предлагал им идиомы старее их рантайма. На учебном стенде это вывернуто наизнанку — менти читает и правит именно даги.
  - защита от Superset была верна не по устройству, а по сегодняшнему содержимому одного файла настройки.
- Что:
  - target-version в корневом ruff.toml поднят до py313, комментарий переписан.
  - две находки UP017 в дагах починены: datetime.timezone.utc заменён на datetime.UTC.
  - из карты проверок убран пункт про разную строгость дверей — с равными версиями он потерял предмет.
- Проверка:
  - make lint; make config-test — зелёные.
  - make -C generator lint — зелёный; исходники генератора не менялись.
  - находок ruff в дагах теперь ровно четыре, как обещал тикет: два переформата и два UP017.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:58:48 +03:00
ddadminandClaude Opus 5 6a708cd5aa refactor(make): корень — про стенд, генератор — за своей дверью
- Зачем:
  - корневые цели смешивали два уровня: пять из шестнадцати начинались с cd generator.
  - цель, названная общерепозиторной, охватывала 31 файл Python из 34: даги и Superset не видел ни линт, ни типы.
- Что:
  - lint, typecheck, test, docs и inventory переехали в новый generator/Makefile.
  - корневой lint заведён по коду стенда — dags и infra/superset — с явными путями и закреплённой версией ruff.
  - заведён корневой ruff.toml: тот же список правил, target-version по младшему Python в образах стенда.
  - цели корня сгруппированы по использованию, осталось двенадцать.
  - два файла дагов переформатированы под новую проверку.
  - карта проверок, оба README, спека генератора и AGENTS.md приведены к двум дверям.
- Проверка:
  - make lint; make config-test — зелёные.
  - make -C generator lint; typecheck; test — зелёные, 407 тестов.
  - ruff check --show-files: из корня ровно три файла стенда, из generator/ — только его.
  - цена корневого lint замерена (0,4 с) и вписана в карту проверок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:31:07 +03:00
ddadmin a2f5b27d89 refactor(smoke): ограничен по времени опрос keeper, уточнены формулировки
- Зачем:
  - горячее ревью: перевезённая проверка keeper потеряла привычку файла
    ограничивать обращения к контейнерам по времени и глушить их ошибки.
- Что:
  - опрос keeper идёт через keeper_exec с timeout 20s и тихим stderr,
    в отчёте об отказе пустой ответ назван словами.
  - комментарий к проверке и абзац README переписаны на проверяемое
    утверждение: настройки объявлены в compose.yaml, смоук спрашивает,
    дошли ли они до процесса.
  - в пробнике ClickHouse ожидаемой строке возвращено имя expected_rows.
- Проверка:
  - make config-test, make smoke, make smoke-cluster; отдельно проверено,
    что на паузе keeper проверка краснеет, а не виснет.
2026-08-06 14:01:45 +03:00
ddadmin 66490d124e refactor(smoke): срезаны проверки стенда, не служащие менти
- Зачем:
  - проверок стало больше, чем продукта, и росли они из критериев приёмки,
    а не из учебной ценности (#56).
- Что:
  - удалены tests/dag-probes-unit.py, tests/stand-smoke-guards.sh,
    tests/stand-smoke-static.sh и tests/smoke-guards.sh вместе с целью
    make smoke-guards и запуском юнит-тестов в scripts/config-test.sh.
  - из scripts/stand-smoke.sh убран check_env_consistency, туда же переехал
    check_keeper_runtime; счёт проверок остался 25.
  - в пробниках свёрнуты функции _assert_*, комментарий про отложенный импорт
    переписан на причину из документации Airflow и продублирован в test_kafka.
- Проверка:
  - make config-test, make up, make smoke, make smoke-cluster.
2026-08-06 13:06:44 +03:00
ddadmin dd491ebe33 refactor(airflow): пробник Kafka разрезан на запись и чтение
Зачем: пробник Kafka — второй и последний пример DAG в стенде, и после #20
единственный, который не показывает, как здесь пишут код. Одним красным
квадратом он к тому же не отвечал на вопрос, какая половина круга отказала:
брокер не принял маркер или не отдал его обратно.

Что:
- вместо одной задачи check_round_trip две: write_marker пишет маркер и
  возвращает адрес записи, read_marker читает по этому адресу и сверяет;
- адрес и маркер едут между задачами XCom — именованными полями словаря:
  XCom проходит через JSON, и кортеж вернулся бы списком;
- внутри записи адрес собирается NamedTuple RecordAddress — два соседних
  целых в сигнатуре переставляются молча;
- каждая задача заводит своего клиента и закрывает его сама, поэтому часовые
  = None и finally с проверками на None ушли; у продюсера закрыть за собой —
  это flush(): своего close() у него нет, и он же возвращает число
  недоставленных;
- настройки клиентов и все сроки ожидания стали именованными модульными
  константами; безымянных чисел в телах задач не осталось;
- слитное условие доставки разобрано на шесть утверждений, каждое со своим
  именем и своим текстом ошибки;
- успех больше не возвращается из середины цикла: чтение выходит из цикла по
  сообщению или по крайнему сроку, а сверка идёт после;
- шапка файла приведена к форме «Тест проверяет: ...» с абзацем о том, чем
  тест не является; она же уходит в doc_md;
- комментарии стоят ровно в шести местах, где незнакома модель Kafka.

Из настроек консьюмера убран session.timeout.ms: он про членство в группе и
удары сердца координатору, а пробник назначает себе адрес и в группу не
входит — почему его нет, объясняет шапка файла. Поведение не меняется.
Сверено по документации confluent-kafka-python через Context7:
session.timeout.ms описан как срок сессии группы, flush() возвращает число
оставшихся в очереди сообщений.

KafkaProbeTests держался за flush_timeouts == [10, 1], то есть за устройство
finally, которого больше нет. На его место встали две проверки свойств:
продюсер закрыт даже тогда, когда отказала запись, и чтение назначается ровно
на тот адрес, который вернул брокер.

Тело issue #22 поправлено тем же изменением: там было записано «задача
остаётся одна» — это расхождение с тем, о чём договаривались в гриллинге.

Проверка: make config-test зелен; make smoke — оба пробника зелены, красной
осталась только проверка памяти стенда по причине из #21.

Closes #22
2026-07-31 20:10:00 +03:00
ddadmin c130319ac7 docs(airflow): шапка теста ClickHouse объясняет, что он проверяет
Зачем: пробники — единственный пример DAG в стенде, по ним будут учиться.
Читатель начинал с кода, не зная, что тест утверждает и чем он отличается
от боевого кода, — и рисковал скопировать приёмы проверки связности в ETL.

Что: строка описания test_clickhouse развёрнута в список проверяемых
утверждений и оговорку, что образцом ETL этот тест не является. Описание
уходит в doc_md и видно в интерфейсе Airflow. Правило комментариев из #20
строк описания DAG не касается, границы тикета не задеты.

Проверка: make config-test — 4/3/6, ошибок 0. make smoke — 24 проверки
зелены, включая оба пробника; красной осталась только память стенда
(3250 MiB против порога 3242,5), причина известна и разбирается в #21.
2026-07-31 19:15:03 +03:00
ddadminandClaude Opus 5 dc12953cac refactor(airflow): пробник ClickHouse разбит на четыре задачи
Зачем: пробник был одной задачей — в интерфейсе Airflow один красный
квадрат, а место отказа приходилось искать по журналу. Ручная машинерия
проброса и сведения ошибок занимала больше места, чем сама проверка, и
читатель продирался через неё раньше, чем понимал, что пробник проверяет.
Пробники — единственный образец DAG в стенде, по ним будут писать
остальные.

Что: test_clickhouse разбит на prepare_tables, write_marker,
read_from_node_2 и cleanup_tables; маркер и имя принявшей запись ноды едут
между задачами через XCom строками. Снято сведение ошибок: except
BaseException, ExceptionGroup, add_note и накопление ошибок в список;
клиент каждая задача заводит общим помощником и закрывает в finally.
Ноды описаны константой NODES парами «имя для человека — источник для
запроса», булев переключатель и параллельные списки подписей ушли.
Уборка идёт обычным правилом запуска, а не all_done: состояние запуска
Airflow считает по концам графа, и уборка, отработавшая после отказа,
покрасила бы в зелёный запуск с упавшей проверкой — решение записано
в ADR 0003. Комментарии остались в четырёх местах: чтение ноды 2 через
remote(), импорт клиента внутри функции, правило запуска уборки и
автосоздание топика в test_kafka. Малые проверки: заглушка task принимает
обе формы декоратора, проверка сведения ошибок заменена проверками
уборки. Красный путь ищет образец по журналам всех задач последнего
запуска, а не в одном самом свежем.

Проверка: make config-test, make smoke (25 проверок) и make smoke-guards
зелены. Разбитый пробник укладывается в 5 секунд из 120, отведённых
run_airflow_probe, — предел не трогаем.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 18:36:52 +03:00
ddadminandClaude Opus 5 64f3418378 feat(airflow): пробные DAG test_clickhouse и test_kafka вместо демонстрационного
Зачем.
Демонстрационный DAG example_clickstream_hello ничего не проверял: он не
обращался ни к ClickHouse, ни к Kafka, поэтому его зелёный результат ничего
не говорил о стенде. Пробники проверяют связи по-настоящему — и тем же
клиентом, каким будут ходить рабочие DAG.

Что.
- test_clickhouse: пишет строку в ReplicatedMergeTree на ноде 1 и читает её
  с ноды 2 через Distributed. Данные проходят путь «нода 2 → все шарды →
  шард ноды 1», то есть проверяется межшардовое чтение, а не одна нода.
- test_kafka: пишет в постоянный топик сообщение с меткой прогона и
  вычитывает его обратно.
- infra/airflow/Dockerfile: clickhouse-connect 1.6.0 и confluent-kafka
  2.15.0 вшиты в образ, импорт проверяется на сборке — при запуске
  контейнера пакеты не доустанавливаются.
- Проверки: scripts/stand-smoke.sh гоняет оба пробника через API Airflow,
  scripts/config-test.sh разбирает DAG без стенда,
  tests/stand-smoke-guards.sh проверяет красный путь,
  tests/dag-probes-unit.py — модульные проверки разбора.
- README и ADR 0001 обновлены тем же изменением.
- Удалён dags/example_clickstream_hello.py.

Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
make clean; cp .env.example .env; make up — 116 с на чистых томах.
make smoke — пройдено 25, ошибок 0; стенд занимает 2244,0 MiB.
make smoke-cluster — все 8 проверок кластера.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:04:19 +03:00
ddadminandClaude Opus 5 63c42bc068 feat(infra): стенд целиком — Kafka, Airflow 3, Superset и мониторинг одной командой
Зачем: рядом с кластером ClickHouse не хватало остальной платформы, а
поднимать её по кускам — значит каждый раз вспоминать порядок. Теперь
`make up` даёт стенд целиком, а `make smoke` честно отвечает, работает он
или нет.

Что:
- Kafka в режиме KRaft (без ZooKeeper), Postgres под метаданные, Airflow
  3.3 четырьмя сервисами и Superset 6.1 с драйверами ClickHouse;
- мониторинг: Prometheus снимает метрики с обеих нод ClickHouse и keeper,
  Grafana получает подготовленный источник данных;
- подключение Airflow ведёт на ноду 1, подключение Superset — на ноду 2:
  ловушка правильных ошибок, забытый `ON CLUSTER` виден в дашборде сам;
- `scripts/stand-smoke.sh` — сквозная проверка из 24 пунктов: топик в
  Kafka, цели Prometheus, запуск примера DAG через API Airflow, проверка
  подключения Superset и расход памяти против порога 3,4 ГБ;
- `make config-test` — статические ворота: Compose, синтаксис Bash и
  Python, стражи README; стражи smoke проверяют, что отчёт краснеет на
  сломанном стенде и зеленеет после восстановления;
- решения записаны в `docs/adr/0001-stand-services.md`, состав стенда и
  порядок работы — в README.

Проверка: на чистых томах `make clean` → `cp .env.example .env` →
`make up` (1 мин 51 с) → `make smoke` — 24 пройдено, 0 ошибок, 2171 MiB.
Перезапуск `make down` → `make up` → `make smoke` — 24/0. Также зелены
`make config-test` (3/0 и 5/0), `make smoke-cluster` (8/8) и
`make smoke-guards` (3/0 и 3/0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:04 +03:00