- Зачем:
- issue #91 требует решать исход и расхождение заказа в день его рождения.
- Что:
- добавлены три исхода, моменты оплаты и отмены и дельта одной позиции.
- обновлены проверки повторяемости и архитектурное описание заказов.
- Проверка:
- cd generator && make lint && make typecheck && make test (418 passed).
- Зачем:
- холодное ревью Кодексом (линия дефектов) показало дыру в проверке
анонимности: личность, уехавшая строкой в сыром `ecommerce`, проходила
мимо сравнения числовых колонок, и тест оставался зелёным. Дыра
воспроизведена: user_id, дописанный в блок покупки, тест не замечал.
- Что:
- тест анонимности спрашивает двумя способами: числовые колонки дня — со
всеми личностями аудитории, строки покупок целиком — с личностями
покупателей. Сырой блок собирается руками, и лишнее поле в нём теперь
красит проверку.
- Проверка:
- из `generator/`: make lint, make typecheck, make test (417 тестов).
- мутация «дописать user_id в ecommerce покупки»: до правки тест зелёный,
после — красный.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- тикет #90: второй источник должен смотреть на торговую половину дня
глазами бэкенда — заказ есть проекция покупки, а не второе порождение;
учебный результат — два источника согласованы по построению, а не
сверкой.
- Что:
- `commerce` отдаёт вторым выходом покупки дня: номер заказа, корзину,
выручку клиента, промокод и человека за кукой; новых бросков в
подпоток `COMMERCE` не добавилось, события дня не сдвинулись.
- заведена заказная сторона: подпоток `ORDERS` на позиции 2 дерева зерна
(прежнее мёртвое имя `DISCREPANCIES`), ветвление — по дню рождения
заказа; `LATECOMERS` не тронут, его наполнит этап 6.
- новый модуль `orders.py`: деньги заказа целыми копейками —
`items_total` из корзины, `discount` по таблице «код → скидка»
(округление вниз), `delivery` броском по таблице весов на полную длину
дня, `total` = `items_total` − `discount` + `delivery`.
- план состава завёл `person_id` — последним броском когорты, после
паспортов: вторая кука пары повторяет ID своего человека, заказ
показывает его как `user_id`, в событие Метрики он не попадает.
- `world`: окно изменяемости `ORDER_WINDOW_DAYS` = 7 рядом с D0 и поясом,
черновая таблица стоимости доставки.
- слепок дня в тестах сторожит обе половины: заказы сравниваются наравне
с потоком событий.
- доки: имя подпотока заказной стороны и состав денег заказа записаны в
`docs/architecture/orders`, README генератора знает про новый модуль.
- Проверка:
- из `generator/`: make lint, make typecheck, make test (417 тестов);
в корне — make lint.
- опись мира не покраснела: байты восьми дней те же, заказная сторона
события не сдвинула.
Closes#90
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- потребителю ODS нужна точная актуальная версия события без знания о FINAL.
- Что:
- создано ods.event_v над распределённой таблицей событий с FINAL.
- сверка стартового мира переведена на новое представление.
- документация различает физические версии ODS и бизнес-представление DDS.
- Проверка:
- make config-test.
- make check-clickhouse.
Зачем: черновик этапов держал DDS заказа в этапе 3, а принятый набор
docs/architecture/orders/ оставляет модель DDS проектированию этапа 4
(ingestion.md «Не входит», тикет #85 перевешен на #6) — расхождение
всплыло на холодном ревью нарезки этапа (#89).
Что: в разделе 9 этап 3 сужен до STG/ODS и дага приёма, модель DDS
заказа названа работой этапа 4.
Проверка: чтение; нарезка этапа 3 (#90–#96) согласована с этой строкой.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Зачем: датированная спека — событие истории, а устройство компонента — живой
документ; большое полотно плохо грузится и агентом, и человеком (ADR 0011).
Что: вычитание #84 слито с переустройством формы: набор
docs/architecture/orders/ — индекс README и семь файлов по частям устройства
(нарезка по правилу «семь плюс-минус два»); датированные файлы удалены,
ссылки перенацелены, AGENTS.md дополнен правилом подпапки. Приёмка
владельцем #88 пройдена, черновой статус снят из README.
Проверка: холодная сверка миграции свежим тредом — потерь решений нет;
обход относительных ссылок набора — битых нет.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Зачем: локальные рабочие файлы агентов (хэндоффы, обменные артефакты ревью)
живут в репозитории, чтобы переживать перезагрузку, но в git им не место.
Что: .scratch/ в .gitignore.
Проверка: git status не показывает .scratch/.
Зачем: решения развилок #70–#74, #80, #81 карты #69 разошлись по резолюциям
тикетов — цельная картина второго источника нужна в одном месте.
Что: черновик спеки заказов docs/specs/2026-08-16-orders.md и спека приёма
docs/specs/2026-08-16-order-ingestion.md; согласующие правки соседних
документов; правки горячего ревью и двух слепых холодных линий — дефекты
сборки закрыты, назван порядок строк внутри слепка.
Проверка: перепроверка находок теми же ревьюерами — ALL_CLOSED.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Зачем: определение описи разошлось с файлом, который называет. Формула
«счётчики закрытых окон с названным читателем» не покрывает счёт событий по
дням, а он в описи лежит и сегодня, и читатель у него самый живой: make
check-clickhouse сверяет по нему приход в ods.event.
Что: счёт событий вернулся в определение строкой дня; «паспорт» снова
раскрыт скобкой — двумя термами выше стоит «Паспорт куки», и голое слово с
ним сталкивалось; вернулось «единственное, что о мире хранится в git» —
определяющее свойство описи, а не следствие. Строка про слепок и правило
«счётчик только с названным читателем» сохранены.
Проверка: определение сверено с data/world-inventory.json и с резолюцией
«Места заказов в стартовом мире» (#74).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Зачем:
- документация не должна обещать постоянные числа для движущегося мира.
- Что:
- уточнено появление опоздавших заказов в слепках.
- опись ограничена неизменяемым выходом и счётчиками с читателем.
- счётчик пар оставлен плану, но исключён из описи.
- Проверка:
- CONTEXT.md перечитан целиком; git diff --check.
Зачем: взгляд владельца на генератор жил только в разговорах, и каждый
следующий агент восстанавливал его заново или не восстанавливал вовсе.
Без него решения по генератору принимаются не по тому критерию: внутренняя
стройность вместо правдоподобия данных на выходе.
Что: новый раздел «Чем меряется генератор» сразу после «Зачем» — пять
утверждений рамки. Генератор меряется выходом, а не устройством; менти
внутрь не смотрит, поэтому конструкция оправдана только наблюдаемым
эффектом; повторимы эффекты, а не числа, и хеши описи обещают видимость
сдвига, а не постоянство чисел; цена ошибки мала, поэтому оборона дороже
ошибки не заводится; сложность изолирована службой ради дешевизны правки.
Рамка шире этапа 2, поэтому спеки следующих этапов ссылаются на неё, а не
копируют.
Проверка: правка только текстовая, кода не касается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Зачем:
- генератору нужен единый язык для скрытой личности и наблюдаемых идентификаторов.
- Что:
- определены человек, посетитель и пользователь магазина.
- закреплена граница между ClientID кликстрима и user_id заказа.
- Проверка:
- документ перечитан целиком.
Зачем: линия дефектов нашла опору на дефект провайдера, линия постановки —
незаписанный ответ на вопрос 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>
Зачем: модельный день прогонялся только руками, и владельцу нечем было
проверять процессы стенда вживую. Пульт нужен раньше этапа 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>
Зачем: модельный день прогоняется только руками, и владельцу нечем
проверять процессы стенда вживую. Этап 3 без дага не существует вовсе
(ADR 0008), поэтому форму пульта и способ вызова генератора надо решить
раньше кода.
Что: ADR 0009 — работники без расписания (`world_next_day` пачкой,
`world_live_day` в темпе) и выключатель `world_live` без своей работы,
ждущий дочерний прогон триггером, а не сенсором; генератор зовётся
`DockerOperator` в каноническом контейнере, потому что в образе Airflow ему
не жить (Python 3.14 против 3.13); позиция ставится как «последний сыгранный
+ 1» — правило вместо сторожа, и его мягкий худший исход назван. Плата за
сокет докера снята замером: доступ открывается добавлением GID в контейнер,
правка прав на хосте не нужна, а сам GID — локальная настройка в `.env`.
Пакетная автоматика отложена, не отвергнута. Расхождения внесены в спеку
генератора: позицию ставит сыгравший, у живого дня появился выключатель.
В словарь добавлен «пульт мира».
Проверка: `make config-test` зелёный; API Airflow 3.3 (пауза и ручной
запуск, ожидание триггера по идентификатору прогона, привязка сенсора к
логической дате, `DockerOperator`) сверено через MCP Context7; версия
Python в образе и права сокета — замером.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- документация и конфигурация доступа должны говорить только подтверждённое.
- Что:
- ADR приведён к результату межшардового замера.
- объяснены служебный грант и учебный компромисс remote().
- удалены избыточные профили, README связан с ADR.
- Проверка:
- make config-test, make lint, make smoke, make check-clickhouse, make check-services.
- Зачем:
- читателю нужна честная граница между учебными ролями и боевой защитой.
- Что:
- README описывает пользователей, роли и место хранения паролей.
- мастер-спека явно откладывает эксплуатационные меры защиты.
- устаревшие комментарии Compose и пробника приведены к реализации.
- Проверка:
- make config-test, make lint.
- Зачем:
- default должен остаться только учёткой локальных служебных вызовов.
- Что:
- пароль default подставлен из окружения на обеих нодах.
- healthcheck и служебные скрипты передают его внутри контейнера.
- Проверка:
- make config-test, make up, make smoke, make check-clickhouse, make check-services.
- Зачем:
- 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.
- Зачем:
- менти должен увидеть разделение доступа без состояния в томах.
- Что:
- пользователи и роли объявлены файлом с паролями из окружения.
- межшардовые запросы передают пользователя через общий секрет.
- четыре допущения реализации подтверждены в ADR живыми замерами.
- Проверка:
- make config-test, make smoke, make check-clickhouse, make check-services.
Зачем: резолюции wayfinder-тикетов живут комментариями, и правка после
ревью идёт в них — а дока описывала только правку тела issue. Путь
неочевидный: без номера issue, по идентификатору из ленты.
Что: абзац в разделе про правку через API и строка в «Что проверено и
когда».
Проверка: снято живыми запросами при закрытии #72 — резолюция правилась
дважды этой командой.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- развилка этапа 3 стояла нерешённой прямо в разделе 7 мастер-спеки: нужен
ли слепку слой сырья и как заказы попадают из топика в хранилище (#70).
- Что:
- заведён ADR 0008 — байтовый чтец без матвью, слой сырья у заказов
остаётся, в ods.order_snapshot пишет шаг Airflow заменой партиции; топик
orders в одну партицию, чтец на clickhouse-01 без ON CLUSTER.
- мастер-спека приведена в соответствие, разделы 6, 7, 9, 11, 12: сравнение
двух приёмов переписано на «поток против слепка», сенсор дневного батча
снят, первый даг переехал с этапа 5 на этап 3.
- в CONTEXT.md заведены «слепок», «окно изменяемости», «пакетный забор».
- Проверка:
- решение сверено по документации ClickHouse через MCP Context7 12 августа
2026 года; что осталось замерить на стенде — списком в конце ADR 0008.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- до появления ETL и витрин надо выбрать границу боевого реализма в
доступе: один беспарольный default учит нулю, а полноценная защита
стоит эксплуатации, которой стенду не потянуть (#66).
- Что:
- принято четыре пользователя и три роли, объявленные файлами настройки,
без состояния в томах и без SQL-хранилища доступа.
- межнодовое доверие переведено на общий секрет кластера: учётные данные
по репликам подменяют права спросившего правами общей учётки.
- записаны отвергнутые варианты, отложенные меры защиты и четыре
проверки, которые закрывает тикет реализации.
- устройство модели отдано разделу README «Состав и доступ»: отдельный
справочник по доступу своего содержания сверх ADR сегодня не имеет.
- оговорено, что роль шире прав на слои — образ требует отдельных
разрешений на ON CLUSTER и на чтение системных таблиц (холодное
ревью #78).
- Проверка:
- реализации в этом коммите нет, менять нечему: make config-test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- 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>
- Зачем:
- половина критерия приёмки стояла не там, где решено: предупреждение о ручном равенстве версий ruff адресовано тому, кто правит лок в generator/, а лежало в корневом Makefile.
- довод «генератор — отдельная сущность» был выписан трижды почти дословно.
- Что:
- равенство версий и охват корневой цели названы в карте проверок; три примечания к таблице собраны списком.
- названа цена занижения target-version: даги бегут на 3.13 и модернизаций не получают.
- шапка generator/Makefile вырезана, корневая сжата до строки, объяснение в ruff.toml укорочено.
- формулировки в AGENTS.md и README поправлены.
- Проверка:
- make lint; make config-test — зелёные.
- make -C generator lint; typecheck — зелёные; исходники генератора не менялись.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- корневые цели смешивали два уровня: пять из шестнадцати начинались с 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>
- Зачем:
- устранены дублирование значений и тихая подстановка неполной настройки.
- Что:
- версии образов и внутренняя топология закреплены рядом с местом использования.
- имя экземпляра, внешние порты, учётные данные и ключи сделаны обязательными настройками .env.
- быстрый старт, Dockerfile и статическая проверка приведены к новой границе.
- Проверка:
- make config-test.
- docker build для образов Airflow и Superset без аргументов.
- make up; make smoke; make check-clickhouse; make check-services.
- Зачем:
- фраза «Date не участвует вовсе» выводила тип из-под общего правила:
про объявление это правда, про употребление — нет. Считая время по
EventDate без имени пояса, легко получить часы вне диапазона.
- Что:
- фраза заменена на две: Date хранит только номер дня, пояс при счёте
времени называют руками, промах виден по часам за границами 0–23.
- Проверка:
- замерено на стенде: без имени пояса часы от начала суток идут -4…19,
отрицательных 15 843 события; с названным поясом — 0…23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- константу COUNTER_TIMEZONE не читал ни один модуль, единственным её
читателем был тест про неё же; связь имени и смещения держится тем, что
они стоят в одной строке.
- Что:
- COUNTER_TIMEZONE снят, имя пояса ушло комментарием к
COUNTER_TIMEZONE_MINUTES.
- тест сходимости имени и смещения снят вместе с ним; test_world.py
вернулся к прежнему виду.
- Проверка:
- make lint, make typecheck, make test (407 тестов)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- линия дефектов нашла три неверных утверждения и мёртвый замер, линия
уместности — три пересказа уже сказанного.
- Что:
- «тип колонки не решает, какое число ляжет» сужено до правды: разбор
отдаёт готовое число, а пояс приёмника решал бы судьбу строки.
- замер до правки типов помечен как неповторяемый на нынешнем стенде.
- правило о поясе сервера привязано к местам, где линза что-то решает:
матвью приёма пояс не называет, и это не нарушение.
- убраны: пересказ механики в ADR 0005, четыре строки учебного
комментария, утверждение о порядке файлов и «секунды от начала эпохи»
у миллисекундной метки.
- Проверка:
- make lint, make typecheck, make test (408 тестов)
- make clean && make up && make check-clickhouse — 9 из 9
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- конвенция #63 записана, а код её не достиг: колонки времени стояли без
пояса, и сходилось всё лишь потому, что пояс сервера — UTC.
- Что:
- UTCEventTime объявлен DateTime('UTC'), служебные метки _load_ts и
kafka_timestamp — DateTime64(3, 'UTC') в STG и ODS.
- parseDateTimeOrNull получил третьим аргументом 'UTC': маска сверяет
суффикс Z как букву, зоны из строки не берёт вовсе.
- контракт схемы и описание выгрузки несут тип с поясом; имя пояса
Europe/Samara встало рядом со смещением в world.py, сходимость сверяет
тест.
- учебный комментарий о линзе — у первой колонки с явным поясом.
- Проверка:
- make lint, make typecheck, make test (408 тестов)
- make clean && make up && make check-clickhouse — 9 из 9
- замер тикета повторён: под session_timezone='Europe/Samara' колонка
показана 2026-05-31 23:37:00, как и без настроек
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Зачем:
- раздел «Часовые пояса» прошёл два холодных ревью — по дефектам и по
уместности. Первое поймало ложный замер и три расхождения с живым
стендом, второе — материал не своей зоны и дубли (#63).
- Что:
- замер «расхождение живёт по HTTP» отозван: мерил toString(UTCEventTime)
в родном клиенте против голой колонки по HTTP, а это разные вещи.
Перемерено — клиенты ведут себя одинаково; записан верный факт: вывод
колонки идёт по поясу сессии, функция — по поясу типа.
- «по поясу сервера» заменено на «по поясу сессии, а тот по умолчанию
серверный» — в разделе и в ADR 0005; утверждение в ледгере переписано
под измеренный раскол вывода и типа.
- PARTITION BY toDate(_load_ts) больше не выдаётся за уже соблюдённое
правило: _load_ts сегодня DateTime64(3) без пояса.
- абзац ADR 0005 больше не спорит с цитатой вызова строкой выше.
- вырезано: веер отклонённых вариантов под заголовком (живые отказы
разложены прозой по своим абзацам, как принято в этом документе),
ссылка на несуществующую связку в world.py, осиротевшая строка про
Grafana, абзац про пояс показа — он уехал комментарием в #63.
- Проверка:
- make lint
- замеры повторены на живом стенде 8 августа 2026 года
- DDL к конвенции по-прежнему не приведён: документы описывают цель
- Зачем:
- пояс в стенде нигде не назван: числа верны только потому, что сервер
ClickHouse стоит в UTC, а правило понадобится в dds и витринах —
воронки, удержание, «покупки по дням» (#63).
- Что:
- раздел «Часовые пояса»: пояс — линза, называется в типе колонки либо в
вызове; какая именно — решает слой (ODS на языке выгрузки, DDS и витрины
на языке бизнеса); день берётся из EventDate.
- названы оба перехода, где пояс выбирается, включая разбор строки в
матвью — он берёт пояс у сервера и тип колонки этого не чинит.
- отвергнутые варианты прозой: умолчание сервера, TZ серверу, ODS в поясе
счётчика, хранение местного времени.
- три замера ушли в «Что проверено», сверка с документацией — от 8 августа.
- Проверка:
- make lint
- DDL к конвенции ещё не приведён: документ описывает цель, код идёт
следом тем же тикетом.
Зачем: формулировка в спеке дублировала строку карты проверок и делала это
хуже оригинала. «make up работает» следует из зелёных проверок и потому
пусто; настоящее условие — «с нуля» — в спеке не проговаривалось. Заодно
гигиеническая планка носила имя приёмки этапа, хотя про предмет этапа она
молчит: дашборд может быть не нарисован, а все три цели зелены. Тот же промах
разобран в ADR 0004 — там он случился с числовым порогом.
Что: раздел 9 спеки отсылает к карте проверок вместо своей формулировки и
называет вещь своим именем — гигиена, а не приёмка. Копия строки убрана из
семи карт этапов и карты #1 в трекере: одна вещь — одно место.
Проверка: правка документная. grep по docs, README и AGENTS — других копий
формулировки нет.
Зачем: этап 2 принят прогоном с нуля, и два его следа должны остаться в
доках — иначе решение «не проверяем» станет забытым долгом, а цена цели
разойдётся с замером.
Что:
- в спеке v2 раздел 11 больше не держит Kafka Engine на двух нодах: дубли и
раскладку партиций между прогонами решено не проверять — дубль возможен по
устройству движка, окно разобрано в доке хранилища, в ODS его схлопывает
ReplacingMergeTree. Половина, которую показал #37, названа;
- в карте проверок цена check-services 44 с -> 59 с и абзац с замерами
приёмки: clean+up 2 м 58 с, смоук 9 с, check-clickhouse 8 с, test 71 с.
Причина подорожания не выдумывается — названа неизвестной.
Проверка: make clean && make up, затем make smoke, make check-clickhouse,
make check-services, make lint, make typecheck, make test — всё зелёное
7 августа 2026 года.
Зачем
Холодное ревью нашло три места, где написанное сильнее сделанного.
Что
- Непустая таблица брака больше не выдаётся за доказательство сломанного
разбора. Модельного дня у брака нет, обрамить его нечем, и строки прежних
уроков лежат в нём месяц: после первого же урока с мусором проверка
давала бы неверный диагноз навсегда. Теперь она даёт признак, по которому
причину отличают, — сошлась недостача с числом брака или нет.
- Комментарий у world-init обещал, что расхождение числа дней с
STARTING_DAYS поймают счётчики. Это неправда в одну сторону: лишний день
ложится за рамкой дат описи. Обещание убрано, дыра названа.
- Довод «даг next_day этапа 5 продолжит ось» опирался на несуществующий
этап; заменён настоящей причиной — заливка замыкает цепь разовых служб.
- Потолок ожидания 300 с получил обоснование замером с кратностью, а сам
скрипт — честную оговорку: его обещание работает на пустом стенде, на
живом ждать нечего.
- Третья, пропущенная ссылка на снятый порог скорости дня убрана из спеки.
- Даты замеров в карте целей разведены: #42 менял три цели, а не шесть.
Проверка
Обе ветви диагноза сняты заново на живом стенде: без брака — «не доехали»,
с браком — признак различения. Стенд восстановлен, все 9 проверок зелёные,
брака 0. make lint, typecheck, config-test, test (407 тестов) зелёные.
Ссылка: #42
Зачем
Стенд поднимался пустым, и всякая приёмка следующих этапов начиналась с
ручной заливки данных. Теперь `make up` сам приводит стенд к одному и тому
же состоянию, а в git лежит то, чем это состояние проверяется.
Что
- Опись мира `data/world-inventory.json`: паспорт (зерно, версия
генератора, хеш каталога) и по строке на каждый из восьми дней — дата,
число событий, хеш байтов. Собирается `make inventory`, свежесть сторожит
`test_inventory.py` — тем же способом, что свежесть описания выгрузки.
- Разовая служба `world-init` вышла из-под профиля и играет в топик восемь
дней при каждом подъёме; зависимый у неё — `airflow-init`, иначе `--wait`
считает успешно отработавшую службу упавшей.
- `scripts/wait-for-world.sh` — вторая половина `make up`: приём
асинхронный, поэтому ждать надо доезда до `ods.event`, а не завершения
заливки. Ограниченный цикл опроса, не пауза наугад.
- Девятая проверка `make check-clickhouse`: подневный счёт событий против
описи, рамка по датам стартового мира, счёт через `FINAL`. При
расхождении называет, где искать, — в событиях или в браке.
- Порог «день ≤ 30 с» снят из спеки генератора в обоих местах: замер дал
1,7 с, порог был выше факта в восемнадцать раз. На его месте — замеры с
датой. Раздел 9 спеки закрыт: открытых вопросов не осталось.
- Слова: «манифест» стал описью мира, «зерновой мир» — стартовым миром
(решение владельца). Оба заведены в словарь CONTEXT.md.
Проверка
`make clean && make up` с нуля — 2 м 50 с, доехало ровно 401 185 событий.
`make check-clickhouse` зелёный (8 с), `make smoke` зелёный (9 с),
`make test` — 407 тестов за 71 с, `make lint`, `make typecheck`,
`make config-test` зелёные.
Что проверка умеет краснеть, снято двумя поломками: снос партиции
2026-06-03 дал диагноз «не доехали до ODS», негодная строка в сырье —
«сломан разбор». Строки опыта убраны, день переигран, счёт вернулся.
Тест свежести проверен молчаливой правкой цены в каталоге: покраснел.
Ссылка: #42
- Зачем:
- находка «это стоило бы проверять регулярно» решалась заново в каждом
тикете и каждый раз тянулась в make check-clickhouse. У неё есть
назначенный дом: даги качества данных, которые придут со следующими
уровнями хранилища.
- Что:
- добавлен раздел «Корректность процессов живёт в дагах DQ, а не в целях
make»: цели make отвечают «стенд собран», свойства данных — работа дага.
- назван фильтр: про полноту дня, свежесть слоя, сходимость витрины с
источником — это даг, а не цель.
- названа учебная сторона: даг идёт по расписанию, пишет историю проверок
и разбирается как обычная задача Airflow — так качество данных устроено
в бою.
- Проверка:
- make config-test
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>