- Зачем:
- этап 2 начинается с формы: контракт схемы — источник истины и для
генерации событий, и для DDL хранилища, а имена пакета и модулей
задают границы всем следующим тикетам этапа.
- Что:
- заведён uv-проект generator/ (pyproject.toml и uv.lock в git; numpy,
pytest и ruff), пакет clickstream_generator.
- schema.py — контракт: чистые данные о 47 колонках выгрузки (имя
Метрики, тип ClickHouse, тип numpy, имя для DDS, группа); порядок
несёт сам кортеж COLUMNS, отдельного поля с номером нет намеренно.
- schema_doc.py собирает из контракта описание выгрузки
docs/formats/clickstream-event.md — по нему пишется сторона
хранилища; документ руками не правится.
- тесты: инварианты контракта (состав, уникальность, заполненность,
согласие типов и порядок групп) и свежесть описания выгрузки.
- цели make lint, make test и make docs; README, AGENTS.md и
CONTEXT.md дополнены генератором, форматами и словарной статьёй.
- Проверка:
- make test (248 тестов), make lint, make config-test;
- make docs, затем git diff --exit-code docs/ — пусто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- владелец сознательно вывел DWH из списка избегаемых (синоним
хранилища), а правка по холодному ревью вернула его по старому
комментарию тикета #32 — откат к воле владельца.
- Что:
- в статье «Хранилище» список «_Избегать_» снова: склад, склад данных.
- Проверка:
- вычитка CONTEXT.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- холодное ревью (Fable, свежая сессия) нашло невыполненный хвост
тикета #32 и места, где доводы резолюций сжались до непонятности.
- Что:
- мастер-спека 1.1: «склад» заменён на «хранилище» (хвост #32);
CONTEXT.md: DWH в избегаемых, отдельная статья «Пакетный режим».
- спека: восстановлены доводы «на маке и в WSL тоже» и «менти упрётся
в красный чек манифеста»; обещания про diff привязаны к манифесту;
темп ×60 и расчёт порога согласованы с числами разделов; заголовок
притока честен про затухание; выход за мандат оговорён в «Зачем».
- раздел 9: добавлены числа притока и календарная дата-константа D0;
заметка исследования: у Faker единицы «значений/с», не «строк/с».
- Проверка:
- вычитка; решения развилок не пересматриваются, правки текстовые.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- вычитка спеки на приёмке нашла дыру: состав мира читался как
замкнутая труппа без новых посетителей — неправдоподобный магазин.
- Что:
- раздел 1: состав не замкнут — план задаёт календарь появления кук,
доля одноразовых высока; приток — часть плана, не мутация.
- раздел 6: «Faker» ослаблен до «посточные фейкеры (Faker, mimesis)»,
выбор библиотеки — этапу 2; там же оговорка про таблицы-литералы.
- раздел 9: к интерфейсу запуска добавлены вопросы «кто зовёт
генератор (даги world_init/next_day)» и «в каком контейнере живёт».
- CONTEXT.md: в «Состав мира» добавлен календарь появления.
- Проверка:
- вычитка; правки точечные, решения развилок не пересматриваются.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- этап 2 нельзя нарезать на тикеты без единой картины генератора,
а решения четырёх развилок карты #26 жили только в тикетах трекера.
- Что:
- новая спека docs/specs/2026-08-01-generator.md: функциональный мир,
детерминизм до байта (SeedSequence сверен через Context7), контракт
схемы, канонический сериализатор, числа и порог производительности;
отклонённые варианты записаны с доводами.
- мастер-спека согласована тем же коммитом: 1.4 — data contract вместо
автогенерации DDL, 8 — в git только манифест, 11 — числа вместо
«зафиксировать требования»; мелкие согласования в 7 и 9.
- CONTEXT.md пополнен терминами модели мира и вывода генератора.
- Проверка:
- вычитка; относительные ссылки спек указывают на существующие файлы
в docs/specs/ и docs/research/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем: практика веера жила только в заметках карты #26 и памяти агента —
хрупко и локально для одной машины. Строку в AGENTS.md читает каждая
сессия, включая чартинг будущих wayfinder-карт.
Что: подраздел «Развилки решений» в Agent skills — существенные развилки
вести через скилл brainstorm-with-docs: веер вариантов, потом конвергенция.
Состав веера и запись отклонённых — в самом скилле, без дублирования.
Проверка: чтение. Сам скилл версионируется в dotfiles владельца.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем
tests/docs-guards.sh сличал README с образцами текста: одна проверка требовала
двух подряд идущих строк дословно, другая — что в отчёте написано «ГБ», а не
«GB». Это тесты на вёрстку абзаца, а не на факт: перестановка слов красит их
в красный, хотя ничего не сломано. README всё равно предстоит переписать
целиком, когда стенд дорастёт до менти, и тогда эти сторожа краснели бы на
здоровом изменении.
Из той же семьи была проверка в stand-smoke-static.sh, требовавшая, чтобы
в scripts/stand-smoke.sh существовал комментарий определённой формулировки.
Что
- удалён tests/docs-guards.sh и его запуск из цели config-test;
- из tests/stand-smoke-static.sh убрана проверка наличия комментария,
счётчик итога приведён к двум оставшимся.
Оставлены обе содержательные проверки stand-smoke-static.sh: отказ на
недоступных compose.yaml и .env.example и то, что скрипт не виснет в сломанном
окружении.
Ссылка на удалённый файл в ADR 0004 намеренно не правится: там записано, что
было сделано в тот день, и подчищать записи решений под сегодняшнее дерево
значит перестать им верить.
Проверка
make config-test — зелено, 5 и 2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем
Комментарии к правке были размером с объяснение, хотя объяснение уже лежит
в ADR 0004. В compose.yaml четыре строки на одну настройку; в stand-smoke.sh
одиннадцать новых строк там, где на весь файл до этого было две — шебанг и
одна строка про разбор подстановок. Заодно в комментариях остались метафоры
(«бронь», «предохранители»), вычищенные из ADR прошлым коммитом.
Что
- compose.yaml: одна строка вместо четырёх — почему не гигабайт и куда идти
за подробностями.
- stand-smoke.sh: две строки вместо шести — зачем проверка вообще нужна.
Комментарий про разбор `--format` убран целиком: он оправдывался перед
читателем, а не помогал ему.
- Комментарий про OOMKilled оставлен, но в одну строку: без него сообщение
«убило процесс, а не контейнер» выглядит опиской.
Проверка
make config-test — зелено.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем
Двухосевое ревью нашло в PR фактическую ошибку и одну процессную дыру. Ошибка
того же класса, что уже снималась по ходу разбора: в ADR было записано, будто
RSS ноды полз вверх из-за страниц её бинарника. Замер на живой ноде это
опроверг.
Что
- ADR 0004: причина дрейфа переписана по замеру. За обычную сессию страницы
бинарника 523 -> 531 МиБ, то есть стоят на месте, а рабочая память
489 -> 723 МиБ. Бинарник объясняет постоянную часть расхода, а не рост;
отчего растёт рабочая память, для этого решения знать не нужно. Вывод не
меняется: одна только постоянная часть занимала больше половины гигабайтной
коробки.
- ADR 0001: ресурсный довод отозван прямо в файле — и строкой статуса, и
абзацем после самого довода. Обе оси ревью нашли это независимо друг от
друга: строка «удерживает стенд в пределе 3,4 ГБ» читалась как действующая,
хотя предела уже нет.
- stand-smoke.sh: OOMKilled поднимается и тогда, когда ядро убило процесс
внутри живого контейнера, поэтому сообщение говорит про процесс, а не про
контейнер. Флаг hurt переименован в problems и считает находки — как passed
и failed по соседству.
- Формулировки ADR 0004 упрощены: «коробка» объясняется при первом упоминании,
а метафоры «вход в самонастройку», «предохранители», «бронь», «полка» и
«бюджет в новой одежде» заменены обычными словами. Правило AGENTS.md —
сложную мысль пояснять при первом упоминании.
Проверка
make config-test — зелено. make smoke — 25 из 25, проверка выживания отработала
с новым сообщением.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем
Стенд упирался в память ноды ClickHouse: пробник валился на CREATE TABLE
ON CLUSTER, вместе с ним краснели make smoke и make smoke-guards. Причина не
та, что предполагал #21: дело не в заводских кэшах, а в коробке на гигабайт.
Около 550 МиБ RSS праздной ноды — страницы её собственного бинарника, и на
работу оставалось около 350 МиБ, которые пробник добирал за сессию.
Заодно выяснилось, откуда взялся предел 3,4 ГБ. Это была оценка расхода из
спеки, посчитанная по стенду-предшественнику до первой сборки v2 и превращённая
в жёсткий порог проверки. Порог стал критерием приёмки каждого этапа и дальше
блокировал бы любой рост стенда на этапах 2-9.
Что
- ADR 0004: бюджета памяти у стенда нет, есть требование к машине — около 8 ГБ,
доступных Docker. Ресурсный довод ADR 0001 отозван, сами решения в силе.
- Нодам ClickHouse 4 ГиБ вместо гигабайта. Остальные лимиты не тронуты: ни один
из них ни разу не сработал, а снять их скопом — то же изменение без
свидетельств, каким они были выставлены.
- Из make smoke убрана проверка суммарного потребления. Она мерила docker stats
вместе со страничным кэшем, то есть отвечала на вопрос «сколько файлов стенд
потрогал», и с появлением настоящих данных краснела бы на здоровом стенде.
Вместе с ней убрана привязанная к её сообщению проверка docs-guards.
- Взамен smoke спрашивает у Docker, не убивало ли ядро долгоживущий контейнер
за память и не включалась ли политика перезапуска. Порога у проверки нет:
убитый контейнер Docker поднимает сам, и без этого вопроса стенд отрапортует
«всё хорошо» о ноде, которая умирала.
- README и раздел «Ресурсный бюджет» спеки переписаны с предела на требование
к машине; README объясняет менти, что такое «память, доступная Docker».
Проверка
make config-test; make up; make smoke — 25 из 25; make smoke-cluster — 8 из 8;
make smoke-guards — 3 из 3, включая шаг «после восстановления стенд проходит
make smoke», который падал 31 июля.
На живом стенде с новой коробкой: max_server_memory_usage = 3,60 ГиБ, в журнале
ноды «Lowered mark cache size to 2.00 GiB because the system has limited RAM».
Семантика счётчиков Docker снята отдельными контейнерами: ручной restart
оставляет RestartCount = 0, убийство за память даёт OOMKilled = true и растущий
счётчик, убийство не за память OOMKilled не поднимает.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Зачем: пробник 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
- Зачем:
- плейбук слепого ревью зовёт craftsman на вкусовую реализацию и
линию уместности, но в репозитории было только два субагента из трёх;
без определения агент приезжает на эффорте сессии, а ступень задаётся
только фронтматтером.
- Что:
- заведён .claude/agents/craftsman.md: Opus, effort medium, исполнение
готовых брифов и слепое ревью.
- Проверка:
- ls .claude/agents/ — три определения; craftsman виден в списке типов
агентов после перезапуска сессии.
Зачем: пробники — единственный пример 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.
Зачем: пробник был одной задачей — в интерфейсе 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>
Зачем: строчка спеки описывала только цели Prometheus, и этап 9 по ней
честно сделал бы девопсовый минимум. Разбор мониторинга предшественника
показал, что там из 28 панелей на вопросы дата-инженера отвечают шесть, а
свежесть, доля брака и сходимость Kafka с ClickHouse не измеряются вовсе.
Состав панелей решается до этапа 9: урок можно рассказать только про то,
что дашборд показывает.
Что: добавлен ADR 0002 — дашборды «данные», «кластер» и «запросы»;
ClickHouse подключается в Grafana источником данных, панели пишутся на SQL;
Prometheus сжимается до тонкого пола, сборщик метрик Airflow через StatsD
не берётся. Инфраструктурные панели сохранены отдельным дашбордом:
«слишком много частей» — ошибка дата-инженера, а видна она именно там.
Плагин источника данных ставится сборкой своего образа Grafana, а не при
старте контейнера: иначе стенд начинает зависеть от сети. Строка объёма в
спеке и пункт этапа 9 указывают на ADR.
Проверка: make config-test — зелено. Отсутствие источника данных ClickHouse
в образе Grafana подтверждено запросом к живому стенду.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
Правило склеивало два разных требования. Различие родословных нужно для
покрытия классов ошибок, свежая сессия — для независимости от рассуждений
автора; из склейки следовал вывод, что линию уместности надо отдавать
просто тому, кто не писал код. На вкусовой линии это ломается: если писал
Opus, вторым оказывается Codex, у которого вкуса нет. В прогоне #18 он вёл
линию по коду с полными руками и вернул пустой вердикт по коду
неправильной формы.
Что.
Линия дефектов назначается по различию, линия уместности — по умению.
Плата названа прямо: общая слепота автора и ревьюера одной родословной
проходит дважды; где цена ошибки этого не терпит — третья модель.
Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
В заметке было сказано, что Gitea не понимает русские ключевые слова, но не
было сказано, что делать вместо этого. На тех же граблях наступили снова:
PR #19 слился, issue #18 остался открытым.
Что.
Правило: писать в теле PR английское `Closes #NN`; ручное закрытие остаётся
запасным путём, если ключевого слова не было.
Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
Учебная ценность записана как базовая, но вывода о выборе исполнителя из
неё не делалось. Прогон #18 показал цену пропуска: код технически
безупречен и при этом неправильной формы — монолитная задача в DAG, ручная
машинерия там, где у Airflow есть свой механизм, ноль комментариев к самому
неочевидному решению.
Что.
Новый раздел «Кому что поручать»: то, что человек будет читать и разбирать,
пишет модель с чувством меры; техническая работа без вкусовых решений — за
Codex; делить работу по подзадачам при планировании, вкусовая часть первой.
Про ревью сказано прямо: когда вкусовую линию ведёт та же родословная, что
писала код, независимость частично теряется — это плата за вкус, и гасится
она свежей сессией и другой линзой.
Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
Учебная ценность записана как базовая, но вывода о выборе исполнителя из
неё не делалось. Прогон #18 показал цену пропуска: код технически
безупречен и при этом неправильной формы — монолитная задача в DAG, ручная
машинерия там, где у Airflow есть свой механизм, ноль комментариев к самому
неочевидному решению.
Что.
В «Цель репозитория» добавлено правило: всё, что человек будет читать и
разбирать, пишет модель с чувством меры; техническая работа без вкусовых
решений — Кодексу; линии ревью всегда разных родословных. Отдельно
отмечено, что экономия лимитов на читаемом коде ложная.
Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
Сторож `tests/docs-guards.sh` падал через `set -e`: единственным следом была
единица в коде возврата, а какое именно утверждение о README перестало быть
правдой — не сообщалось. Со стороны файл выглядел набором несвязанных grep
без объяснения, зачем каждый из них нужен.
Отдельно: `make smoke-cluster` мог зависнуть навсегда. У запроса INSERT
clickhouse-client дочитывает данные из стандартного ввода и ждёт его конца;
при запуске не из терминала, а из фонового процесса с открытым вводом конец
не наступает никогда. Проверка молча висела больше двадцати минут.
Что.
- Проверки собраны в именованные функции, каждая с комментарием, зачем она
существует и что ломается, когда она краснеет.
- Обёртка `check` печатает утверждение и при успехе, и при провале, считает
пройденные и выходит с понятным сообщением.
- Ввод запросов ClickHouse закрыт через `</dev/null`, рядом — объяснение
причины.
- README: раздел «Какую проверку когда запускать» — лесенка от дешёвой
статической проверки к дорогой интеграционной, с ответом, зачем внутри
`make smoke-guards` три прогона `make smoke`.
Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Фальсификация сторожа: порт ноды 2 в README изменён — «ОШИБКА: не
подтвердилось: README перечисляет HTTP- и нативные порты обеих нод», код 1;
двоеточие в строке про перезапуск ClickHouse заменено на тире — «ОШИБКА: не
подтвердилось: README требует перезапуск ClickHouse после изменения настройки
метрик», код 1. README восстановлен из индекса.
make smoke-cluster с открытым стандартным вводом — 8 проверок за 7 с; до
починки та же команда висела 23 минуты и была снята вручную.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Зачем.
Демонстрационный 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>
- Зачем:
- инженерные скиллы (to-tickets, triage, wayfinder, domain-modeling) ждут
репо-локальную настройку; без неё они не знают, чем заводить issues и
какими метками размечать. Дока трекера жила ссылкой на репозиторий
предшественника — внешняя зависимость на месте источника истины.
- Что:
- заведён docs/agents/: issue-tracker.md (Gitea через tea, команды,
wayfinding, отдельный раздел про то, что «Закрывает #NN» issue не
закрывает), triage-labels.md (пять канонических меток без переименований)
и domain.md (один контекст, CONTEXT.md и docs/adr/).
- в AGENTS.md добавлен раздел «Agent skills» со ссылками на эти файлы,
ссылка на доку предшественника заменена локальной.
- в «Структуре» закреплён нейминг: ГГГГ-ММ-ДД-слаг для docs/specs/ и
docs/research/, NNNN-слаг для docs/adr/.
- Проверка:
- tea labels list — все пять меток триажа заведены в репозитории;
- tea api version — Gitea 1.27.0, команды из доки отвечают живьём.
Зачем: рядом с кластером 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>
- Зачем:
- конвейер работы над тикетом раздаёт рассуждения и механику разным
субагентам; определение в репозитории задаёт модель и глубину явно и
переживает сессию.
- Что:
- .claude/agents/deep-reasoner.md — рассуждения и ревью, модель opus.
- .claude/agents/fast-worker.md — механическая работа, модель sonnet.
- Проверка:
- .gitignore уже разрешает .claude/agents/; субагенты видны в списке
агентов Claude Code.
- Зачем:
- этап 1 спеки требует стенд, поднимаемый одной командой; кластер —
единственный режим, выключателя «без кластера» нет (issue #12).
- Что:
- compose.yaml: две ноды ClickHouse и отдельный clickhouse-keeper на
зафиксированном LTS-образе 26.3.17.56, порты только на 127.0.0.1.
- infra/clickhouse: общее описание кластера, подключение к keeper и
отдельные макросы shard и replica для каждой ноды.
- scripts/clickhouse-smoke.sh: восемь проверок ON CLUSTER от описания
кластера до удаления временных таблиц, вывод по-русски.
- tests/smoke-guards.sh: три проверки самой smoke-команды —
ограниченная аварийная очистка, обработка прерывания, окружение keeper.
- README.md: быстрый старт, роли нод, обоснование выбора версии.
- Проверка:
- make up && make smoke && make smoke-guards && make clean
Зачем: при нарезке этапов выяснилось, что образцы DAG'ов из v1 написаны под
Airflow 2 и переносятся не буквально — в третьей версии Datasets
переименованы в Assets. Без явной записи в спеке версия всплыла бы уже при
написании DAG'ов этапа 5.
Что: в раздел «Проверить при исполнении» добавлен пункт про Airflow 3.x —
версия фиксируется на этапе 1 (каркас стенда), DAG'и этапа 5 пишутся под API
третьей версии, операторы и сенсоры сверяются через Context7.
Проверка: правка текстовая, только спека; тот же порядок продублирован в
тикетах этапов 1 и 5 в трекере.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Зачем:
- Claude Code сам не читает AGENTS.md; без CLAUDE.md сессии в v2 стартуют без контракта репозитория.
- Что:
- однострочный CLAUDE.md с директивой @AGENTS.md.
- Проверка:
- новая сессия Claude Code в каталоге v2 показывает контракт из AGENTS.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Зачем:
- спека «Боевой реализм стенда» исполняется в новом репозитории:
предшественник замораживается как стабильный учебный стенд,
v2 стартует пустым и переносит только нужное
- Что:
- README: что это, статус «строится по спеке», ссылки на спеку и на
репозиторий-предшественник
- AGENTS.md написан заново, а не скопирован: язык, uv, обязательная
проверка API через MCP Context7, контракт трекера Gitea (спека —
источник истины, корневой issue тонкий), метки триажа, новые доки в docs/
- .gitignore: Python и uv, .env, секреты, IDE, логи
- docs/specs/2026-07-30-stand-v2-realism.md перенесена из v1; содержание
не менялось, поправлены только ссылки: добавлена строка о переезде,
ссылка на generator-realism.md переведена на абсолютный URL v1
- docs/research/2026-07-26-yandex-clickstream-format.md перенесено:
источник истины по формату широкого события
- лицензии у предшественника нет, переносить нечего
- Проверка:
- git show --stat: 5 файлов
- относительные ссылки спеки ведут на существующие файлы репозитория