docs(glossary): четыре термина переименованы, world.md и stand.md вычитаны

- Зачем:
  - справочники читает человек со стороны, а «пульт мира», «опись мира»,
    «работник» и «сырьё» звучали шуткой или калькой времён разработки
    (тикет #214, выбор владельца).
- Что:
  - CONTEXT.md: статьи «Паспорт мира», «Даги модельного времени»,
    «Шаговый даг», «Сырые данные», старые слова в строках «Избегать»;
    пометки в ADR 0005, 0008, 0009 без переписывания решений.
  - замены по README, справочникам, путеводителю, лабам, спекам,
    комментариям дагов и SQL, тегу дага и панели Grafana; имена в коде
    не тронуты.
  - world.md и stand.md: 35 находок вычитки внесены, регистр выровнен.
- Проверка:
  - make config-test; make lint в корне и в generator/; make test в
    generator/ (437 passed); скрипт ссылок из #212 пуст; grep старых
    слов вне ADR, research и CONTEXT.md пуст.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QBCtU38qSHg5DvgtnKXcW
This commit is contained in:
ddadminandClaude Fable 5.1 committed 2026-09-02 16:46:50 +03:00
1 parent 86ad8afaf2
commit 59d8c83b17
54 files changed
+794 -776

No files matched your search

+64 -68
View File
@@ -272,12 +272,13 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
Классы пересекаются — приоритет: `cancelled` > `lost_event` >
`duplicate_event` > `amount_delta` > `match`.
Пятое — **опоздание** — бесплатно даёт формат доставки: часть заказов
впервые появляется в слепке D+1/D+2 («вчера не сходилось, сегодня сошлось»),
ориентир ~10%. Точные доли зафиксированы замером на каноническом мире
(28 дней, 2026-08-30, #194) — запись в «Что проверено»
[карты проверок](../architecture/testing.md); опись хранит счётчики наблюдаемых классов после приоритета — в заказах и
только по дням с закрытым окном (раздел 8).
Пятое — **опоздание** — бесплатно даёт формат доставки: часть заказов впервые
появляется в слепке D+1/D+2 («вчера не сходилось, сегодня сошлось»), ориентир
~10%. Точные доли зафиксированы замером на каноническом мире (28 дней,
2026-08-30, #194) — запись в «Что проверено» [карты
проверок](../architecture/testing.md); паспорт хранит счётчики наблюдаемых
классов после приоритета — в заказах и только по дням с закрытым окном (раздел
8).
Не берём: сироту-фрод (`purchase` есть, а заказа не будет никогда) —
механически дублирует B.
@@ -311,14 +312,13 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
запуск пишет полный текущий набор различных пар; предыдущий результат не
служит источником следующего.
- **N:1**: часть покупателей покупает с двух кук («телефон и ноутбук») —
параметр мира; значение фиксирует эта спека: 15%, детерминировано
от seed. Ядро лабы:
`uniq(посетителей) > uniq(людей)`, менти выводит расхождение сам.
Константа мира: каждый двухкуковый покупатель делает минимум по одному
заказу с каждой куки — иначе вторая кука не попадает в карту соответствий
(она строится только из покупок) и лаба не воспроизводится. Число таких
пар растёт, пока мир едет, и в опись не кладётся
([что хранит опись](../architecture/orders/inventory.md)).
параметр мира; значение фиксирует эта спека: 15%, детерминировано от seed.
Ядро лабы: `uniq(посетителей) > uniq(людей)`, менти выводит расхождение сам.
Константа мира: каждый двухкуковый покупатель делает минимум по одному заказу
с каждой куки — иначе вторая кука не попадает в карту соответствий (она
строится только из покупок) и лаба не воспроизводится. Число таких пар растёт,
пока мир едет, и в паспорт не кладётся ([что хранит
паспорт](../architecture/orders/inventory.md)).
- Витрины разводят имена честно: **«посетители»** (`uniq(ClientID)`) и
**«известные пользователи»** (после склейки) — оба числа рядом в дашборде.
@@ -339,16 +339,14 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
ноду 2**. Это осознанная ловушка правильных ошибок: забытый ON CLUSTER или
VIEW поверх локальной таблицы проявляются в дашборде сами.
- **Приём Kafka**: у событий — Kafka-таблицы и MV на обеих нодах, одна
consumer group, 2 партиции на топик `hits`; MV пишут в Distributed-цели.
У заказов приём другого рода — пакетный забор без MV, чтец на одной ноде,
топик `orders` в одну партицию ([ADR 0008](../adr/0008-order-ingestion.md)).
Раскладку в обоих случаях решает ключ:
события — по `cityHash64(ClientID)` (см. 1.3), заказы —
`cityHash64(order_id)`, сырьё STG — `cityHash64(сырой строки)`; полный
список и доводы — в [доке хранилища](../architecture/storage.md). Урок
«какая нода читала топик — меняется между прогонами, куда легли данные —
нет» живёт на событиях: при пакетном заборе читает та нода, которую
спросили.
consumer group, 2 партиции на топик `hits`; MV пишут в Distributed-цели. У
заказов приём другого рода — пакетный забор без MV, чтец на одной ноде, топик
`orders` в одну партицию ([ADR 0008](../adr/0008-order-ingestion.md)).
Раскладку в обоих случаях решает ключ: события — по `cityHash64(ClientID)`
(см. 1.3), заказы — `cityHash64(order_id)`, STG — `cityHash64(сырой строки)`;
полный список и доводы — в [доке хранилища](../architecture/storage.md). Урок
«какая нода читала топик — меняется между прогонами, куда легли данные — нет»
живёт на событиях: при пакетном заборе читает та нода, которую спросили.
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
@@ -444,23 +442,24 @@ README.
| DM | витрины `dm.*_v`, `dm.dq_summary` | см. ниже |
У каждой таблицы слоя — пара из локальной и распределённой, имена по конвенции
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырья —
см. [доку хранилища](../architecture/storage.md).
Потребительские запросы и витрины читают сущности DDS через `_v` с явным
списком колонок; `_rep` и `_dist` служат загрузке, диагностике и обслуживанию.
Договор держится конвенцией, а не правами: модель доступа описана в
[ADR 0007](../adr/0007-clickhouse-access.md), договор чтения — в
[доке хранилища](../architecture/storage.md), «Имена объектов».
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырых данных
— см. [доку хранилища](../architecture/storage.md). Потребительские запросы и
витрины читают сущности DDS через `_v` с явным списком колонок; `_rep` и `_dist`
служат загрузке, диагностике и обслуживанию. Договор держится конвенцией, а не
правами: модель доступа описана в [ADR 0007](../adr/0007-clickhouse-access.md),
договор чтения — в [доке хранилища](../architecture/storage.md), «Имена
объектов».
Заказы принимаются **пакетным забором** ([ADR
0008](../adr/0008-order-ingestion.md), [ADR
0010](../adr/0010-order-versions-in-ods.md)): чтец топика байтовый, как у событий, но
матвью к нему не привязана, и сырьё забирает шаг, которым управляет Airflow —
одно прямое чтение вставляет порцию в `stg.orders_raw` с `_load_id` запуска.
Один следующий `task_id` двумя последовательными запросами пишет годные строки
и ошибки из того же среза. `ods.order` выбирает текущую версию по максимальной
`snapshot_date` ([ADR 0014](../adr/0014-order-version-snapshot-date.md)). Слой
сырья остаётся точкой повтора и разбора одной принятой порции.
0010](../adr/0010-order-versions-in-ods.md)): чтец топика байтовый, как у
событий, но матвью к нему не привязана, и порцию в STG забирает шаг под
управлением Airflow — одно прямое чтение вставляет её в `stg.orders_raw` с
`_load_id` запуска. Один следующий `task_id` двумя последовательными запросами
пишет годные строки и ошибки из того же среза. `ods.order` выбирает текущую
версию по максимальной `snapshot_date` ([ADR
0014](../adr/0014-order-version-snapshot-date.md)). Слой Сырой слой остаётся
точкой повтора и разбора одной принятой порции.
Состав служебных колонок задаёт дока хранилища. Спеке важны два следствия:
`ods.event` и `ods.order` получают метку загрузки `_load_ts`, но версию
@@ -531,31 +530,28 @@ Kafka день переигрывается генератором заново:
пачками), Postgres остаётся только служебной базой Airflow. Смешанность
ландшафта выражена режимами и частотами, а не второй трубой.
## 8. Эталонный мир и опись
## 8. Эталонный мир и паспорт
В git хранится только опись эталонного мира; сам снимок генерируется на
месте — при `make up` и при проверках (решение развилки «Производительность»
карты #26, подробности — [спека генератора](2026-08-01-generator.md),
раздел 5). Снимок — 8 дней, совпадает со стартовым миром: обещание в 14 дней
снято на этапе 7. Опись несёт
паспорт мира (каноническое зерно, версия генератора), контрольные счётчики
и хеши по дням; проверки «пустой git diff» и «пересгенерируй день N —
сравни хеш» живут на ней. Политика версионирования артефакта (бывший туман
карты #10) закрыта этим же ходом: версионируется опись.
Контрольные числа описи:
В git хранится только паспорт эталонного мира; сам снимок генерируется на месте
— при `make up` и при проверках (решение развилки «Производительность» карты
#26, подробности — [спека генератора](2026-08-01-generator.md), раздел 5).
Снимок — 8 дней, совпадает со стартовым миром: обещание в 14 дней снято на этапе
7. Паспорт несёт шапку с каноническим зерном и версией генератора, контрольные
счётчики и хеши по дням; проверки «пустой git diff» и «пересгенерируй день N —
сравни хеш» живут на нём. Политика версионирования артефакта (бывший туман карты
#10) закрыта этим же ходом: версионируется паспорт. Контрольные числа паспорта:
- по строке на каждый отправленный слепок — хеш байтов: побайтовое обещание
«слепок переснимается и даёт те же байты» опись сторожит наравне с днями;
«слепок переснимается и даёт те же байты» паспорт сторожит наравне с днями;
- заказная сторона: счётчики наблюдаемых классов расхождений после
приоритета; каждый показывает число заказов и хранится только для дня с
закрытым окном (читатель — техническая проверка `classes_vs_inventory`
звена DQ; при N сыгранных днях таких дней N − 7). Отдельных полей для
общего числа заказов и выручки в описи пока нет: для них не назван
читатель;
закрытым окном (читатель — техническая проверка `classes_vs_inventory` звена
DQ; при N сыгранных днях таких дней N − 7). Отдельных полей для общего числа
заказов и выручки в паспорте пока нет: для них не назван читатель;
- контрольные числа идентичности не заводятся: uniq известных пользователей
и число двухкуковых покупателей растут, пока мир едет, а счётчик без
названного читателя в опись не кладётся
([что хранит опись](../architecture/orders/inventory.md)).
названного читателя в паспорт не кладётся ([что хранит
паспорт](../architecture/orders/inventory.md)).
Размер снимка замерен на этапе 7 — запись в разделе «Что проверено»
[карты проверок](../architecture/testing.md).
@@ -572,7 +568,7 @@ v2 стартует пустым, поэтому объём ниже — это
| SQL | DDL по слоям и ролям (ON CLUSTER, Replicated*, Distributed; раскладка файлов — в доке хранилища) + трансформации событий, заказов, identity, сверки + словарь | L — ~12–15 файлов, главная сложность |
| Airflow | DAG'и по образцу v1: etl_pipeline (партиционная переобработка), world_init/next_day, helpers; первый даг — переливка слепка — приходит раньше, этапом 3 | M — ~5–6 файлов |
| Superset | датасеты + дашборд с тремя новыми сюжетами | M — 2 файла |
| Эталонный мир | пересборка снимка на месте, счётчики описи, чек-скрипты | M–L |
| Эталонный мир | пересборка снимка на месте, счётчики паспорта, чек-скрипты | M–L |
| Мониторинг | дашборды Grafana «данные», «кластер», «запросы»; ClickHouse источником данных, панели на SQL; Prometheus тонким полом (ADR 0002) | M — конфиги и дашборды |
| Документация | доки v2 пишутся заново (см. раздел 12) | M, в тех же PR |
@@ -619,8 +615,8 @@ v2 стартует пустым, поэтому объём ниже — это
4. Трансформации и витрины: модель DDS заказа, сессии, identity_map,
выручка, сверка A+C.
5. Airflow: `etl_pipeline` (партиционная переобработка).
6. Расхождения B+D и опоздания; счётчики описи.
7. Эталонный мир: опись и пересборка снимка, чек-скрипты. CI-генерация
6. Расхождения B+D и опоздания; счётчики паспорта.
7. Эталонный мир: паспорт и пересборка снимка, чек-скрипты. CI-генерация
снята (спека генератора, раздел 5).
8. Superset-дашборд v2.
9. Мониторинг и runbook «keeper упал / DDL повис в очереди». Состав дашбордов
@@ -658,15 +654,15 @@ ADR 0002 с приёмочными совпадают почти целиком.
расхождение часов клиент/коллектор — туман карты, вернётся своим тикетом.
- Лабы и курс: v2 — другой стенд, лабы для него пишутся с нуля отдельной
работой после этой спеки; редизайн лаб v1 (#7) остаётся в v1 и сюда не
переносится. Исключение — домашки слоя DM: ТЗ витрин менти и даг
самопроверки забраны в этап 4 резолюцией #135, потому что витрина менти и
конвейер, в который она встает, проектируются вместе. Сюда же —
необязательная лаба «свой дашборд в Superset» (#197): она стоит на
витринах домашек DM и продумана до готового ТЗ в этапе 8, тогда как
остальные лабы пока на уровне идей. Единый стиль блока
домашек — за работой учебного пути. Спека даёт будущим лабам только
опорные точки — контрольные числа описи (сверка, идентичность). Явное
следствие: после этапа 9 стенд работает, но учебного пути на нём ещё нет.
переносится. Исключение — домашки слоя DM: ТЗ витрин менти и даг самопроверки
забраны в этап 4 резолюцией #135, потому что витрина менти и конвейер, в
который она встает, проектируются вместе. Сюда же — необязательная лаба «свой
дашборд в Superset» (#197): она стоит на витринах домашек DM и продумана до
готового ТЗ в этапе 8, тогда как остальные лабы пока на уровне идей. Единый
стиль блока домашек — за работой учебного пути. Спека даёт будущим лабам
только опорные точки — контрольные числа паспорта (сверка, идентичность).
Явное следствие: после этапа 9 стенд работает, но учебного пути на нём ещё
нет.
- Инкрементальный ETL (#8) — свой issue.
- Реплики (2×2), HAProxy, репликационная эксплуатация — в лекцию, не в стенд.
- TLS между нодами, настоящее хранилище и ротация секретов, внешние учётные
+132 -139
View File
@@ -41,10 +41,10 @@
конструкция внутри оправдана только наблюдаемым эффектом: без него это
сложность, за которую никто не заплатил.
- **Повторимы должны быть эффекты, а не конкретные числа.** Правка свойств
мира сдвигает почти все числа разом — генератор случайных чисел выдаёт
другую последовательность. Опираться на конкретные числа поэтому нечем.
Хеши описи обещают не постоянство чисел, а то, что их сдвиг не пройдёт
молча: пересобрал опись — увидел diff.
мира сдвигает почти все числа разом — генератор случайных чисел выдаёт другую
последовательность. Опираться на конкретные числа поэтому нечем. Хеши паспорта
обещают не постоянство чисел, а то, что их сдвиг не пройдёт молча: пересобрал
паспорт — увидел diff.
- **Цена ошибки здесь мала.** В худшем случае испорчен урок, и чинится он
правкой генератора после взгляда на получившиеся распределения. Оборона,
которая дороже ошибки, не заводится.
@@ -59,15 +59,15 @@
модельный день D — функция (зерно, D). Между прогонами живут только зерно
и позиция на оси времени.
- **Детерминизм до байта.** Одно зерно — побайтово тот же снимок; сверка —
хешами описи. Транспорт (офсеты Kafka, темп) — вне обещания.
хешами паспорта. Транспорт (офсеты Kafka, темп) — вне обещания.
- **Схема — контракт генератора.** Python-модуль с чистыми данными;
хранилище строится по рендеренной документации, границу сторожит строгий
приём на стороне хранилища.
- **Один сериализатор, глупые приёмники.** День-функция выдаёт канонические
байты; приёмники — файл, Kafka пачкой, Kafka с темпом.
- **Числа.** Средний день ~50 тыс. событий; эталонный снимок совпадает со
стартовым миром — 8 дней;
в git — только опись мира; автоматических порогов по времени нет.
стартовым миром — 8 дней; в git — только паспорт мира; автоматических порогов
по времени нет.
## 1. Модель мира
@@ -109,23 +109,21 @@
начинается в D0) — только дают, кому возвращаться в первые дни: дневная
аудитория на полке с самого D0, разгона «пустого магазина» нет.
- **Гарантия двухкуковых пар — назначенные заказы в плане.** Единица
здесь — человек, не кука: план помечает часть людей покупателями
(доля — раздел 9), и 15% покупателей (мастер-спека, раздел 5) получают
вторую куку. Обе куки пары принадлежат одному человеку одной когорты;
вторая рождается в пределах его окна активности, без фиксированного
зазора — по тому же затухающему профилю, что и возвраты (уточнение при
исполнении #38, 2026-08-02): куку человек заводит, пока ещё ходит.
Равные шансы по всему окну означали бы вторую куку у давно ушедшего
человека — и вчетверо меньше пар, реализованных внутри снимка.
Каждой паре план назначает дни гарантированных заказов: по
одному на куку, из дней активности этой куки, на оси от D0 и позже;
человеку предыстории, чьё окно активности таких дней не оставляет,
пара не назначается. День-функция обязана назначенные заказы
реализовать; остальные покупки — вольные, их решает генератор торговых
событий (#40). План считает пары, реализованные в запрошенном горизонте;
опись это число не хранит — оно растёт вместе с миром. Условность в данных
не видна: дни назначены той же случайностью, просто брошенной планом один
раз.
здесь — человек, не кука: план помечает часть людей покупателями (доля —
раздел 9), и 15% покупателей (мастер-спека, раздел 5) получают вторую куку.
Обе куки пары принадлежат одному человеку одной когорты; вторая рождается в
пределах его окна активности, без фиксированного зазора — по тому же
затухающему профилю, что и возвраты (уточнение при исполнении #38,
2026-08-02): куку человек заводит, пока ещё ходит. Равные шансы по всему окну
означали бы вторую куку у давно ушедшего человека — и вчетверо меньше пар,
реализованных внутри снимка. Каждой паре план назначает дни гарантированных
заказов: по одному на куку, из дней активности этой куки, на оси от D0 и
позже; человеку предыстории, чьё окно активности таких дней не оставляет, пара
не назначается. День-функция обязана назначенные заказы реализовать; остальные
покупки — вольные, их решает генератор торговых событий (#40). План считает
пары, реализованные в запрошенном горизонте; паспорт это число не хранит — оно
растёт вместе с миром. Условность в данных не видна: дни назначены той же
случайностью, просто брошенной планом один раз.
- **Своя ось модельного времени.** Ось событий начинается в
фиксированный день D0 (понедельник — см. раздел 5); реальный
календарь в модели не участвует. В `EventDate`/`UTCEventTime` дни оси
@@ -133,10 +131,10 @@
зависящая (значение — раздел 9). Между прогонами живут только зерно и
позиция на оси: выключенный ноутбук — мир замер, потом продолжил.
- **Два режима движения по одной оси.** Пошаговый — базовый для лаб: старт
с эталонного снимка, дальше «прожить следующий день» — явное действие.
Живой день — текущий день проигрывается с ускорением, дашборд и мониторинг
«дышат»; включается по требованию — постоянным фоном идёт, только пока
включён выключатель пульта ([ADR 0009](../adr/0009-world-control.md)).
с эталонного снимка, дальше «прожить следующий день» — явное действие. Живой
день — текущий день проигрывается с ускорением, дашборд и мониторинг «дышат»;
включается по требованию — постоянным фоном идёт, только пока включён
выключатель `world_live` ([ADR 0009](../adr/0009-world-control.md)).
- **Граница суток — единственный структурный шов.** Сессии режутся по ней,
дневная партиция самодостаточна; в конце модельного дня — слепок заказов.
День проживается целиком, полдня не бывает: недожитый из-за обрыва день
@@ -148,14 +146,13 @@
Отклонено с доводами:
- *Мутирующее состояние мира* («мир стареет»): ломает параллельность по
дням, требует чекпоинтов, счётчики описи узнаваемы только постфактум;
ни один урок стенда на старении не стоит.
дням, требует чекпоинтов, счётчики паспорта узнаваемы только постфактум; ни
один урок стенда на старении не стоит.
- *Чистая функция без слоя состава*: глобальные инварианты пришлось бы
выводить в каждом дне заново — тот же план мира, но неявный и размазанный.
- *Привязка модельного времени к реальному календарю* (T-1 с догоном):
конфликтует с ускорением ×60 — за вечер мир уезжает в будущее — и делает
даты эталонного мира зависимыми от даты запуска, опись теряет
воспроизводимость.
конфликтует с ускорением ×60 — за вечер мир уезжает в будущее — и делает даты
эталонного мира зависимыми от даты запуска, паспорт теряет воспроизводимость.
Отклонено при исполнении #38 (2026-08-02):
@@ -170,9 +167,9 @@
константы — потолка одновременно живущих кук, которого в жизни нет и
который менти нечем объяснить.
- *Вероятностная гарантия пар* («почти наверняка купит с обеих кук»): не
гарантия — однажды опись покраснеет, а чинить нечем, кроме смены
зерна; при этом несклеенная пара в данных неотличима от двух незнакомцев,
так что реализм этой лотереи невидим.
гарантия — однажды паспорт покраснеет, а чинить нечем, кроме смены зерна; при
этом несклеенная пара в данных неотличима от двух незнакомцев, так что реализм
этой лотереи невидим.
- *Покупка пары в первый визит куки*: гарантия железная и дешёвая, но узор
«все пары покупают в первый день» виден в данных ровно там, куда лаба
склейки смотрит пристальнее всего. Условность допустима, пока она не
@@ -185,31 +182,29 @@
- **Обещание — содержимое до байта.** Два прогона с одним зерном дают тот же
набор событий: те же `WatchID`/`VisitID`, поля, метки модельного времени.
Снимок при пересборке побайтово совпадает: канонический порядок ключей и
строк; сверка — по хешам описи, а два локально пересобранных снимка
сравнимы обычным diff — пустой означает «ничего не изменилось». Вне
обещания — транспорт: офсеты и партиции Kafka, какая нода прочитала,
`_load_ts`, темп живого дня.
строк; сверка — по хешам паспорта, а два локально пересобранных снимка
сравнимы обычным diff — пустой означает «ничего не изменилось». Вне обещания —
транспорт: офсеты и партиции Kafka, какая нода прочитала, `_load_ts`, темп
живого дня.
- **Условия обещания.** Детерминизм держится при зафиксированном `uv.lock`
и внутри канонического контейнера — то есть везде Linux, на маке и в WSL
тоже; единственная переменная — архитектура CPU. Истина — CI на Linux; сходимость любой машины проверяет
скрипт «пересгенерируй день N — сравни хеш с описью». Расхождение на
любой платформе — баг генератора, а не допуск.
и внутри канонического контейнера — то есть везде Linux, на маке и в WSL тоже;
единственная переменная — архитектура CPU. Истина — CI на Linux; сходимость
любой машины проверяет скрипт «пересгенерируй день N — сравни хеш с
паспортом». Расхождение на любой платформе — баг генератора, а не допуск.
- **Раздача зерна — иерархией подпотоков.** Корневое зерно → подпоток
состава мира, ветвящийся по номеру дня на когорты плана (состав
спрашивается по дням — раздел 1; уточнение при исполнении #38).
Дни предыстории отрицательны, а позиция в дереве — неотрицательное
целое, поэтому у предыстории своя ветвь состава, отдельная от оси
(уточнение при исполнении #38, 2026-08-02);
(зерно, день) → подпоток дня → именованные подпотоки компонентов: трафик,
торговые события, заказная и событийная стороны (судьба заказов и порча
событийного потока) — в фиксированном порядке (уточнение [судьбой
заказа](../architecture/orders/fate.md): прежние «расхождения» и
«опоздания» были названы по классам витрины, а компонент называет часть
мира).
По построению: параллельный прогон равен последовательному; продление
истории днём N+1 не трогает дни 1…N; правка одного компонента меняет
только его часть снимка — в описи меняются хеши только затронутых
дней, дифф двух локальных пересборок читаем.
состава мира, ветвящийся по номеру дня на когорты плана (состав спрашивается
по дням — раздел 1; уточнение при исполнении #38). Дни предыстории
отрицательны, а позиция в дереве — неотрицательное целое, поэтому у
предыстории своя ветвь состава, отдельная от оси (уточнение при исполнении
#38, 2026-08-02); (зерно, день) → подпоток дня → именованные подпотоки
компонентов: трафик, торговые события, заказная и событийная стороны (судьба
заказов и порча событийного потока) — в фиксированном порядке (уточнение
[судьбой заказа](../architecture/orders/fate.md): прежние «расхождения» и
«опоздания» были названы по классам витрины, а компонент называет часть мира).
По построению: параллельный прогон равен последовательному; продление истории
днём N+1 не трогает дни 1…N; правка одного компонента меняет только его часть
снимка — в паспорте меняются хеши только затронутых дней, дифф двух локальных
пересборок читаем.
- **Механизм подпотоков — `numpy.random.SeedSequence`.** Сверено через
Context7 по документации numpy (2026-08-01): `spawn(n)` порождает детей
расширением `spawn_key`, потомок полностью определяется парой
@@ -223,8 +218,8 @@
представление в клиентском `purchase` (урок мастер-спеки о расхождениях).
- **Канонический seed и паспорт мира.** Эталонный мир собирается одним
каноническим зерном — константой репозитория; свои зёрна менти крутит без
гарантий описи. Опись хранит паспорт мира — зерно и версию
генератора; чек-скрипты сверяют паспорт раньше счётчиков.
гарантий паспорта. В шапке паспорта лежат зерно и версия генератора;
чек-скрипты сверяют шапку раньше счётчиков.
- **Суточный профиль интенсивности задаёт день-функция.** Форма — волны:
ночной провал, обеденный и вечерний пики, различие будней и выходных;
пики — до ~2× среднего. С детерминизмом профиль совместим: это часть
@@ -241,8 +236,8 @@
«параллельно равно последовательно» недостижимо.
- *Обещание детерминизма поверх обновления зависимостей*: numpy сознательно
улучшает алгоритмы распределений между версиями (NEP 19), Faker меняет
словари. Фиксация — `uv.lock`; обновление зависимостей — осознанная
пересборка описи одним PR.
словари. Фиксация — `uv.lock`; обновление зависимостей — осознанная пересборка
паспорта одним PR.
## 3. Контракт схемы
@@ -289,9 +284,9 @@
- **Один канонический сериализатор, глупые приёмники.** День-функция выдаёт
упорядоченный поток канонических байтов — единственное место, где событие
превращается в JSON. Приёмники не знают о содержимом: файл (локальный кэш
для пересборки и проверок описи), Kafka пачкой — пакетный режим,
Kafka с темпом ×60 — живой день. Новых топиков нет.
превращается в JSON. Приёмники не знают о содержимом: файл (локальный кэш для
пересборки и проверок паспорта), Kafka пачкой — пакетный режим, Kafka с темпом
×60 — живой день. Новых топиков нет.
- **Одно событие — одно сообщение Kafka.** «Пачкой» относится к темпу
отправки, а не к упаковке: приёмник шлёт события подряд без пауз, но каждое
отдельным сообщением. Контракт транспорта, не деталь реализации — сторона
@@ -299,9 +294,9 @@
([ADR 0005](../adr/0005-event-ingestion.md)), поэтому склейка нескольких
событий в одно сообщение сломала бы разбор целиком.
- **Даты и время на проводе — ISO-8601.** `EventDate` уезжает как `2026-06-01`,
`UTCEventTime` — как `2026-06-01T12:34:56Z`. Довод — читаемость сырья: весь
смысл слоя STG в том, что менти открывает колонку `raw` в обычном клиенте и
разбирает событие глазами, а число эпохи этот урок убивает. Колонка
`UTCEventTime` — как `2026-06-01T12:34:56Z`. Довод — читаемость сырых данных:
весь смысл слоя STG в том, что менти открывает колонку `raw` в обычном клиенте
и разбирает событие глазами, а число эпохи этот урок убивает. Колонка
`ecommerce` — строка, внутри которой лежит экранированный JSON, как отдаёт
Метрика.
@@ -321,9 +316,9 @@
первым. Записана форма всё равно здесь — иначе каждый выберет своё, и
разойдётся это уже после приёмки.
- **Рабочий выбор сериализатора — orjson**: быстрее stdlib json в 5–14 раз,
numpy-массивы и datetime сериализует нативно (заметка исследования #31).
Смена библиотеки меняет канонические байты, поэтому проходит как
обновление зависимости: осознанная пересборка описи одним PR.
numpy-массивы и datetime сериализует нативно (заметка исследования #31). Смена
библиотеки меняет канонические байты, поэтому проходит как обновление
зависимости: осознанная пересборка паспорта одним PR.
- **Промежуточные файлы не хранятся.** Файл дня — кэш чистой функции:
потерял — пересчитал. В git снимок не попадает (раздел 5).
- **Обрыв любого режима — переигровка дня целиком**; дедуп склеивает
@@ -341,7 +336,7 @@
- *Отдельный топик / Kafka как хранилище дней*: офсеты и партиции вне
обещания детерминизма, retention конечен, в git топик не положишь, хеш с
описью не сверишь; Kafka на стенде — труба, не хранилище (раздел 7
паспортом не сверишь; Kafka на стенде — труба, не хранилище (раздел 7
мастер-спеки).
- *Файл как обязательная станция доставки*: доигрывание обрыва уже решено
через дедуп, канон держит единственный сериализатор, а не диск; файл
@@ -360,27 +355,25 @@
~2× среднего, будни/выходные; ≈8–12 тыс. сессий, 6–8 тыс. посетителей —
правдоподобный средний магазин.
- **Эталонный снимок совпадает со стартовым миром — 8 дней**: понедельник
по понедельник, полная неделя и один закрытый день за окном K = 7.
Обещание в 14 дней снято на этапе 7 (решение владельца 2026-08-30):
у дней 8–13 не нашлось читателя — лабы проигрывают мир дальше без описи,
транспорт байты с описью не сверяет, а проверке «пересгенерируй день N —
сравни хеш» день 3 годится так же, как день 13. Замеры и тесты, меряющие
мир на окне в две недели, не меняются: мир — функция без края, окну
замера опись не нужна. Удлинение описи остается дешевым ходом
(пересборка) и делается, когда читатель появится — например, если
растяжке `classes_vs_inventory` понадобится сверять больше одного
закрытого дня.
- **В git — только опись, снимок не хранится.** Снимок генерируется при
по понедельник, полная неделя и один закрытый день за окном K = 7. Обещание в
14 дней снято на этапе 7 (решение владельца 2026-08-30): у дней 8–13 не
нашлось читателя — лабы проигрывают мир дальше без паспорта, транспорт байты с
паспортом не сверяет, а проверке «пересгенерируй день N — сравни хеш» день 3
годится так же, как день 13. Замеры и тесты, меряющие мир на окне в две
недели, не меняются: мир — функция без края, окну замера паспорт не нужен.
Удлинение паспорта остаётся дешёвым ходом (пересборка) и делается, когда
читатель появится — например, если растяжке `classes_vs_inventory` понадобится
сверять больше одного закрытого дня.
- **В git — только паспорт, снимок не хранится.** Снимок генерируется при
создании мира и в проверках генератора: артефакт — кэш чистой функции, кэш в
git не хранят. Опись несёт паспорт мира, счётчики и хеши по дням; проверки
«пустой git diff» и «пересгенерируй день N — сравни хеш» живут на ней.
Первый `make up` пустого стенда — живая демонстрация детерминизма; готовый мир
повторный подъём не пересчитывает. Честная потеря —
страховка на случай платформенного бага: раньше менти с расходящимися
байтами мог взять готовый снимок из git, теперь он упрётся в красный чек
описи; смягчение — дисциплина целочисленной случайности (раздел 2):
расхождения amd64/arm64 сняты устройством, а не сторожем. CI ради одного
прогона не заводится (решение владельца 2026-08-30).
git не хранят. Паспорт несёт шапку, счётчики и хеши по дням; проверки «пустой
git diff» и «пересгенерируй день N — сравни хеш» живут на нём. Первый `make
up` пустого стенда — живая демонстрация детерминизма; готовый мир повторный
подъём не пересчитывает. Честная потеря — страховка на случай платформенного
бага: раньше менти с расходящимися байтами мог взять готовый снимок из git,
теперь он упрётся в красный чек паспорта; смягчение — дисциплина целочисленной
случайности (раздел 2): расхождения amd64/arm64 сняты устройством, а не
сторожем. CI ради одного прогона не заводится (решение владельца 2026-08-30).
- **Живой день — ×60 по умолчанию**: модельные сутки за 24 реальные минуты,
суточная волна разворачивается на глазах; темп в среднем ~35 событий/с,
в пиковые часы сильных дней — до ~100. Число —
@@ -399,7 +392,7 @@
| Операция | Ориентир |
|---|---|
| Генерация одного дня | 1,7–2,8 с (замер 7 августа 2026 года) |
| Пересборка эталонного мира (8 дней + опись, без транспорта) | 20,5 с (замер 30 августа 2026 года) |
| Пересборка эталонного мира (8 дней + паспорт, без транспорта) | 20,5 с (замер 30 августа 2026 года) |
| Заливка стартового мира при `make up` (8 дней, Kafka → матвью → ODS) | 22,5 с (замер 7 августа 2026 года) |
| Лаг живого дня | секунды |
@@ -460,9 +453,9 @@
- **Раздел 1.4**: «из контракта выводятся DDL и валидация» заменено на data
contract — хранилище пишется по документации, границу сторожит строгий
приём (раздел 3 здесь).
- **Раздел 8**: артефакт `data/startup_history/` в git заменён описью;
снимок генерируется на месте (раздел 5 здесь). Туман «политика
версионирования артефакта» закрыт этим же ходом: версионируется опись.
- **Раздел 8**: артефакт `data/startup_history/` в git заменён паспортом;
снимок генерируется на месте (раздел 5 здесь). Туман «политика версионирования
артефакта» закрыт этим же ходом: версионируется паспорт.
- **Раздел 11**: пункт «до этапа 3 зафиксировать требования
производительности» закрыт числами раздела 5.
- Мелкие согласования там, где текст опирался на артефакт в git: источник
@@ -491,24 +484,25 @@
теперь есть.
- **Этап 5 (Airflow)**: живой день со стороны хранилища — обычный ETL-даг
по расписанию (~раз в 24 минуты); генератор не дорабатывается.
- **Позиция на оси времени** (хвост отдан пульту мира раньше этапа 5 —
- **Позиция на оси времени** (хвост отдан дагам модельного времени раньше этапа
5 —
[ADR 0009](../adr/0009-world-control.md))**.** Проигрыватель состояния не
хранит (раздел 9), поэтому вести позицию — работа того, кто его зовёт. Живёт
она переменной Airflow: отсутствие переменной означает, что стартовый мир
ещё не создан. В обычной жизни её ставит даг пульта, который сыграл день, —
она переменной Airflow: отсутствие переменной означает, что стартовый мир ещё
не создан. В обычной жизни её ставит шаговый даг, который сыграл день, —
значением «последний сыгранный + 1» и только по успеху. Даги жизненного цикла
используют `-1` для незавершённой очистки, `0` для чистого мира до генерации
и ставят первый несыгранный день только после приёма стартового мира
([ADR 0013](../adr/0013-world-lifecycle.md)).
Довод — генератор отдельная и заменяемая сущность, привязывать его к
хранилищу незачем. Полный `make clean` сносит метаданные Airflow вместе с
данными ClickHouse, а узкая пересборка явно сводит сохранённую позицию с
заново залитым миром.
Расхождение переменной с данными (менти почистил партицию руками) лечится
документацией, а не сторожем: постоянная сверка была бы проверкой, красной
в норме ([ADR 0002](../adr/0002-monitoring-scope.md)).
- **Этап 7 (эталонный мир)**: пересборка — это опись, не артефакт.
CI-генерация на amd64 и arm64 снята — резолюция в разделе 5.
используют `-1` для незавершённой очистки, `0` для чистого мира до генерации и
ставят первый несыгранный день только после приёма стартового мира ([ADR
0013](../adr/0013-world-lifecycle.md)). Довод — генератор отдельная и
заменяемая сущность, привязывать его к хранилищу незачем. Полный `make clean`
сносит метаданные Airflow вместе с данными ClickHouse, а узкая пересборка явно
сводит сохранённую позицию с заново залитым миром. Расхождение переменной с
данными (менти почистил партицию руками) лечится документацией, а не сторожем:
постоянная сверка была бы проверкой, красной в норме ([ADR
0002](../adr/0002-monitoring-scope.md)).
- **Этап 7 (эталонный мир)**: пересборка обновляет паспорт мира. Отдельного
артефакта у неё нет. CI-генерация на amd64 и arm64 снята — резолюция в разделе
5.
- **Будущие лабы**: перезаливка дня X пакетным режимом проигрывателя —
готовая демонстрация идемпотентности конвейера.
@@ -521,10 +515,10 @@
[ADR 0017](../adr/0017-order-numbering.md). Новые решения генератора
записывают в ADR и в спеке этапа, на котором их принимают.
Журнал собран по темам, но дата и тикет остались у каждого решения. По
журналу видно, какие границы затрагивала каждая правка: иногда она меняла
сразу план состава, воронку и каталог. При #40 это было дешево — опись еще
не собрали; позже такой ход потребовал бы пересборки описи.
Журнал собран по темам, но дата и тикет остались у каждого решения. По журналу
видно, какие границы затрагивала каждая правка: иногда она меняла сразу план
состава, воронку и каталог. При #40 это было дёшево — паспорт мира ещё не
собрали; позже такой ход потребовал бы пересборки паспорта мира.
### План состава и числа мира
@@ -547,10 +541,10 @@
покупателей ~190 в день, что дает конверсию ~2% на сессию.
- **Конфигурация мира — модуль чистых данных** (#38, 2026-08-02) рядом с
контрактом схемы: все числа мира собраны в одном месте, которое менти может
крутить. Правка модуля меняет мир, и проверка описи честно краснеет: опись
сторожит только канон. Вне модуля остается то, что мира не меняет, — свое
зерно и транспортные флаги проигрывателя. Отклонено: внешний конфиг и
переопределения окружением — переменная мира, которой нет в паспорте описи;
крутить. Правка модуля меняет мир, и проверка паспорта честно краснеет:
паспорт сторожит только канон. Вне модуля остается то, что мира не меняет, —
свое зерно и транспортные флаги проигрывателя. Отклонено: внешний конфиг и
переопределения окружением — переменная мира, которой нет в шапке паспорта;
файл-конфиг в репозитории равен модулю по смыслу, но платит загрузчиком и
валидацией (довод раздела 3 против YAML).
- **Покупатель перестает быть словом — двумя рычагами сразу** (#40, решение
@@ -749,8 +743,8 @@
четвертям цены и по медиане, потому что одни только края списка обманываются
четырьмя исключениями. Хвост соседям: словарь ClickHouse над этим файлом ещё
не построен (#37/#43) — колонку надо взять в его описание сразу, а не догонять
правкой; описи нужен хеш каталога (хвост #42) тем более, потому что теперь в
файле живёт ещё и поведение.
правкой; паспорту нужен хеш каталога (хвост #42) тем более, потому что теперь
в файле живёт ещё и поведение.
- **Доля просмотров карточек, доходящих до корзины, выросла с 7,3% до 8,6%**
(#50, 2026-08-02) и подошла к потолку вилки. Визитам вне `/cart` нужны свои
позиции, а визит с намерением должен класть почти все сравненное; обе границы
@@ -764,7 +758,7 @@
живых магазинов эта доля гуляет широко; пока она правдоподобна, сама по себе
ничего не сторожит, и подгонять поведение под ее край не надо. Настоящий
предел считается в другой валюте — дневной бюджет событий, на котором стоит
опись #42. Если будущая правка упрется в него, двигать надо бюджет и его
паспорт #42. Если будущая правка упрется в него, двигать надо бюджет и его
причины, а не долю.
### Номер заказа, промокод и деньги
@@ -933,28 +927,27 @@
подъем указал бы внутрь `.venv`: каталог был бы в образе, но модуль его не
нашел бы. Проверено в контейнере: день совпал побайтово с прогоном на машине.
### Опись мира и словарь
### Паспорт мира и словарь
- **Опись мира одна** (#42, 2026-08-07; эталонная длина — 8 дней, этап 7,
2026-08-30). Стартовый мир этапа 2 — первые восемь дней,
понедельник по понедельник: полная неделя и первый замкнутый цикл K = 7.
`data/world-inventory.json` — та самая опись, которую обещает раздел 5.
Этап 7 оставил ее восьмидневной: у хвоста в шесть дней не нашлось читателя
(резолюция — в разделе 5); вырастет она продолжением этого же файла, а не
вторым. В JSON лежат
паспорт мира — зерно, версия генератора и хеш каталога — и строки дней с
датой, числом событий и хешем. Опись собирает `make inventory`, свежесть
сторожит `test_inventory.py` тем же способом, что свежесть описания выгрузки.
Отклонено: *два файла, младший и полный* — две правды об одном мире и лишнее
слово в словаре.
- **Паспорт мира один** (#42, 2026-08-07; эталонная длина — 8 дней, этап 7,
2026-08-30). Стартовый мир этапа 2 — первые восемь дней, понедельник по
понедельник: полная неделя и первый замкнутый цикл K = 7.
`data/world-inventory.json` — тот самый паспорт, который обещает раздел 5.
Этап 7 оставил его восьмидневным: у хвоста в шесть дней не нашлось читателя
(резолюция — в разделе 5); вырастет он продолжением этого же файла, а не
вторым. В JSON лежат шапка паспорта — зерно, версия генератора и хеш каталога
— и строки дней с датой, числом событий и хешем. Паспорт собирает `make
inventory`, свежесть сторожит `test_inventory.py` тем же способом, что
свежесть описания выгрузки. Отклонено: *два файла, младший и полный* — две
правды об одном мире и лишнее слово в словаре.
- **Хеш каталога лежит в паспорте и объясняет расхождение** (#42, 2026-08-07).
Правка цены меняет мир так же молча, как правка кода. Хеши дней и без
каталожного хеша поймают смену мира; он отвечает на следующий вопрос — что
менялось: разошлись дни и каталог — CSV, только дни — код.
- **«Манифест» переименован в «опись мира», «зерновой мир» — в «стартовый мир»**
(#42, решение владельца 2026-08-07). Обычные русские слова убирают пару
«манифест / мини-манифест» и необходимость различать две сущности там, где
вещь одна.
(#42, решение владельца 2026-08-07; позднее тикет #214 сменил «опись мира» на
«паспорт мира»). Обычные русские слова убирают пару «манифест / мини-манифест»
и необходимость различать две сущности там, где вещь одна.
### Открытое на момент заморозки
+21 -21
View File
@@ -12,11 +12,11 @@ DDL и двумя одноразовыми службами заливал во
Граница владения не совпадала с процессом, и это видно по следствиям. Успешная
отправка в Kafka выдавалась за созданный мир: `world-init` завершался сразу
после записи в топик, до приёма сообщений хранилищем.
Повторный `make up` смешивал подъём служб с повторной заливкой данных. Узкая
пересборка хранилища сохраняла в Kafka сообщения и офсеты прежнего мира, потому
что чинила только ClickHouse. Позиция пульта правилась снаружи Airflow — тем,
кто миром не управляет.
после записи в топик, до приёма сообщений хранилищем. Повторный `make up`
смешивал подъём служб с повторной заливкой данных. Узкая пересборка хранилища
сохраняла в Kafka сообщения и офсеты прежнего мира, потому что чинила только
ClickHouse. Позиция на оси правилась снаружи Airflow — тем, кто миром не
управляет.
**Чему учит переезд.** Менти видит один наблюдаемый граф, которому принадлежат
прикладная схема, данные мира и позиция модельного времени, — и Compose,
@@ -63,7 +63,7 @@ Airflow держит прикладные топики Kafka, каноничес
Каталог `/opt/airflow/world` — то, что нужно для создания мира: `generator/`,
`data/` и корневой `.dockerignore` смонтированы только для чтения. Из первого
собирается канонический образ генератора, из второго читается опись мира, а
собирается канонический образ генератора, из второго читается паспорт мира, а
последний не пускает в контекст сборки локальные окружения и кэши. Запросы
стенда приезжают прежним монтированием `sql/`.
@@ -101,7 +101,7 @@ Kafka, `SQLExecuteQueryOperator` для исполняемых SQL-файлов
кода: сборкой образа при подъёме больше никто не занимается.
5. Играет стартовые дни одним прогоном `batch` того же образа.
6. Ждёт, пока события доедут до ODS. Условие ожидания то же, что у прежнего
сценария, и оно точное: сумма строк `ods.event_dist` за даты описи и строк
сценария, и оно точное: сумма строк `ods.event_dist` за даты паспорта и строк
`ods.event_errors_dist` не меньше суммы событий из
[`data/world-inventory.json`](../../data/world-inventory.json). «Что-то
приехало» условием не считается.
@@ -136,21 +136,21 @@ Kafka, `SQLExecuteQueryOperator` для исполняемых SQL-файлов
третьим местом с той же последовательностью, а первое же расхождение между
дагами пришлось бы прятать в его параметрах.
### Сторожа у работников мира
### Сторожа у шаговых дагов
`world_next_day` и `world_live_day` краснеют, если позиции нет либо она меньше
`WORLD_STARTING_DAYS`. Отказ называет прочитанное значение и следующий шаг:
поднять стенд или пересобрать хранилище. Выключатель `world_live` своего
сторожа не получает — он краснеет вслед за работником.
поднять стенд или пересобрать хранилище. Выключатель `world_live` своего сторожа
не получает — он краснеет вслед за шаговым дагом.
`orders_ingest` сторожа не получает вовсе: его зовёт сама инициализация, когда
готового мира ещё нет.
## Состояния позиции
`world_position` остаётся единственным состоянием пульта.
`world_position` остаётся единственным состоянием дагов модельного времени.
| Позиция | `world_initialize` | `world_recreate` | Работники |
| Позиция | `world_initialize` | `world_recreate` | Шаговые даги |
|---|---|---|---|
| переменной нет | создаёт мир | очищает и создаёт | красный отказ |
| `-1` | красный отказ: очистка не завершена, повторите `make rebuild-storage` | очищает и создаёт | красный отказ |
@@ -187,9 +187,9 @@ Kafka, `SQLExecuteQueryOperator` для исполняемых SQL-файлов
ClickHouse, они состоят в группах потребителей, и удаление группы недопустимо.
Поэтому сначала удаляются базы, затем топики.
**Позицию удалили руками на живом мире.** Даг честно зальёт мир заново: сырьё
в STG удвоится, ODS схлопнет повтор дедупликацией. Условие ожидания на непустом
ODS истинно сразу — этим оно и ограничено, обещание «доехала именно эта
**Позицию удалили руками на живом мире.** Даг честно зальёт мир заново: число
строк STG удвоится, ODS схлопнет повтор дедупликацией. Условие ожидания на
непустом ODS истинно сразу — этим оно и ограничено, обещание «доехала именно эта
заливка» ни один счёт дать не может. Сторожа на этот случай нет: ошибку менти
делает себе сам и тут же видит.
@@ -197,10 +197,10 @@ ODS истинно сразу — этим оно и ограничено, об
`world_initialize`, тот ждет `etl_pipeline`, а конвейер ждет слой. В живом
режиме такую же цепочку образуют `world_live`, `world_live_day`, `etl_pipeline`
и слой. В обоих случаях три ожидающие задачи оставляют слою последний слот.
Одновременная пересборка добавляет ожидания и загоняет стенд в тупик.
Поэтому пересборка — явная операция обслуживания: оператор ставит `world_live`
на паузу и даёт работникам доиграть. Предусловие остаётся в документации;
механизм блокировок стоил бы дороже этой строки.
Одновременная пересборка добавляет ожидания и загоняет стенд в тупик. Поэтому
пересборка — явная операция обслуживания: оператор ставит `world_live` на паузу
и даёт шаговым дагам доиграть. Предусловие остаётся в документации; механизм
блокировок стоил бы дороже этой строки.
## Границы
@@ -226,9 +226,9 @@ ODS истинно сразу — этим оно и ограничено, об
позиция та же, счётчики `check-clickhouse` те же, в журнале дага сказано, что
мир уже есть.
- **`make rebuild-storage`** возвращает канонический мир: позиция проходит
`-1` → `0` → `WORLD_STARTING_DAYS`, в ODS ровно даты описи, топики созданы
`-1` → `0` → `WORLD_STARTING_DAYS`, в ODS ровно даты паспорта, топики созданы
заново, офсеты групп идут с нуля, DDS и DM собраны.
- **Красные пути проверяются своими отказами.** Сторож работника при
- **Красные пути проверяются своими отказами.** Сторож шагового дага при
отсутствующей, нулевой и отрицательной позиции; `world_initialize` на `-1`;
оборванная очистка, оставляющая `-1`. Проверка, не видевшая своей поломки,
доказывает только то, что умеет печатать «ЗЕЛЁНО».
+6 -4
View File
@@ -104,14 +104,14 @@
### Ступень 6. Разбери сырые события и сверь результат со стендом
Менти пакетно разбирает срез сырья событий в собственную таблицу и построчно
Менти пакетно разбирает срез событий из STG в собственную таблицу и построчно
сверяет результат с нетронутым стендовым ODS. Урок — изучить разбор потока
отдельно от живого приема и использовать стендовый результат как образец для
сверки. Матвью разбора остаются несущей стеной: их пересоздание молча теряет
сообщения.
Форма — «собери рядом». Обратная связь — построчная сверка двух разборов. Ее
границы называются прямо: срок работы ограничен окном хранения сырья, а
границы называются прямо: срок работы ограничен окном хранения сырых данных, а
содержимое неопорных колонок можно сравнить только со стендовым разбором, не с
независимым эталоном.
@@ -150,7 +150,8 @@
- **Какая позиция объясняет разницу в сумме.** Расхождение суммы нужно
разложить до конкретной вычеркнутой позиции и объяснить до копейки.
- **Был ли отмененный заказ оплачен.** Текущее состояние не заменяет историю, а
некоторые различия после истечения срока сырья уже не восстановить.
некоторые различия после истечения срока хранения сырых данных уже не
восстановить.
- **Когда отчет за день можно считать окончательным (#188).** Отчет за день
становится окончательным лишь после того, как источник больше не может
изменить этот день.
@@ -191,7 +192,8 @@
потребителей создает еще одну полную копию потока.
- **Как заметить пропущенные сообщения по офсетам.** Разрыв в офсетах
показывает потерю, которую трудно заметить по самим данным.
- **Почему сырье удаляется по времени загрузки.** Очистка сырья живет по
- **Почему сырые данные удаляются по времени загрузки.** Очистка сырых данных
идёт по
времени загрузки, а не по модельному дню.
- **Куда попадают битые строки.** Годные строки и брак делят поток без
пропусков и пересечений; менти подкладывает по строке каждого класса брака