docs(specs): спека редизайна пути менти — мир из артефакта и две ветки

- Зачем:
  - находки ручного HITL 2026-07-19 требовали проектного решения: путь
    менти через backfill медленный, бедный и путаный; нужна база import
    эталонного мира и две ветки роста.
- Что:
  - спека docs/specs/2026-07-19-mentee-path-redesign.md: целевая модель
    (import + next-day + continue), эталонный 3-дневный мир в git (xz),
    переименование пульта в world_init с дефолтом import, отдельный
    world_next_day, инкрементальные счётчики manifest, один учебный
    профиль; форма работ — 4 дочерних issue.
  - CONTEXT.md: термины «мир (стенда)», «эталонный мир», «три режима
    менти».
  - .scratch/hitl-findings.md восстановлен из среза 0e312b3 как рабочий
    материал фичи (до разбора в issues).
- Проверка:
  - вычитка против hitl-findings и решений обсуждения 2026-07-19.
This commit is contained in:
2026-07-19 23:18:17 +03:00
parent 79a3d07cb1
commit d6e7a03032
3 changed files with 413 additions and 0 deletions
+236
View File
@@ -0,0 +1,236 @@
# Находки ручной HITL-проверки пути менти
Сессия 2026-07-19. Проверяем путь менти своими глазами: Airflow UI ->
Superset -> Kafka UI. Стенд чистый (`make clean` + `make up`), профиль `ci`.
## Рамочная модель: три режима менти (подтверждено, launch.py:117-137)
Основной режим менти — **загрузка из архива**, дальше две ветки от одного
восстановленного состояния (`GEN_STATE_RESET=false`). Это и есть каркас,
в который ложатся все находки ниже. Из каждой ветки можно сделать отдельную
лабораторную.
- **База: `import`** артефакта (целевой размер 3 дня, `daily-wave`) ->
мир на `T_end`. Общий фундамент обеих веток. См. F7, F8.
- **Ветка A: `next-day`** (`GEN_RUN_MODE=next-day`) — пакетно добавить
сутки. Лаба «инкрементальная обработка»: инкремент vs full-refresh,
чистота стыков, расписание. Сюда бьют F1, F3, F6, F9.
- **Ветка B: `continue`** (`GEN_RUN_MODE=live`) — непрерывный живой поток
от той же границы. Лаба «потоковый приём»: near-real-time ETL,
мониторинг Grafana, стык backfill/live. **Требует ×60** — оправдывает
скорость учебного профиля (уточнение к F8).
Важно: `next-day` и `continue` — разные педагогики, не схлопывать.
**Под каждую ветку — свой урок/лаба** (учебный контент, `docs/course/`):
одна про пакетную инкрементальную обработку (`next-day`), другая про
потоковый приём (`continue`). Общая база `import` — их совместное начало.
`backfill`/`reset` (`STATE_RESET=true`, «с нуля»): в **целевой** модели
`backfill` — инструмент мейнтейнера для сборки артефакта, а не первый шаг
менти. Сегодня ещё наоборот — backfill остаётся каноническим первым
прогоном менти (см. F1/F2); этот сдвиг и есть суть редизайна.
## F1. Форма `generator_control` помечает необязательные параметры обязательными (BUG)
- **Где:** Airflow UI -> `generator_control` -> Trigger DAG w/ config.
- **Симптом:** все поля формы (`duration`, `seed`, `model_time_speed`,
`artifact_path`, `expected_t_end`) показаны с красной `*` и обязательны.
Браузерная валидация `required` не даёт отправить форму с пустым полем.
Споткнулись первым на пустом `duration`.
- **Причина:** в `dags/generator_control_dag.py` эти `Param(...)` объявлены
с `type="string"` без `"null"`. Airflow для типа без `null` вешает на input
HTML-атрибут `required`.
- **Противоречие с документами:** `docs/OPERATIONS.md` и описания самих
Param говорят «пусто — взять из профиля / не сохранять». То есть поля
задуманы необязательными, но UI это запрещает.
- **Влияние на менти:** канонический первый прогон «backfill с профилем,
остальное пусто» через UI невозможен без обходного заполнения.
- **Кандидаты решения:** сменить тип необязательных Param на
`type=["null","string"]` (идиома Airflow для необязательной строки) —
проверить актуальность через Context7; либо, как минимум, поправить
формулировку в runbook. Предпочтителен первый.
- **Обход в этой сессии:** заполнили все поля значениями профиля `ci`
(`duration=6h`, `GEN_SEED=4242`, `GEN_MODEL_TIME_SPEED=1`,
`artifact_path=/opt/airflow/data/ci_backfill.json`,
`expected_t_end=2026-01-01T06:00:00+00:00` — для backfill игнорируется).
## F2. Superset: гео-карта заменена столбцами — ПОДТВЕРЖДЕНИЕ, не дефект
- **Наблюдение менти:** на дашборде вместо гео-карты — столбчатый
«Top Countries by Events». На первый взгляд неожиданно.
- **Проверка:** это осознанное решение задачи 10 (`done`). Legacy-виз
`world_map` (choropleth) не даёт настроить tooltip/легенду/шкалу, а
гео-распределение сильно перекошено (US-доминанта из статического сида
`geo_by_click_id`, своя генерация гео — отдельный шаг по ADR-0006).
Топ-N стран столбцами читается лучше карты. Критерии приёмки задачи 10
это фиксируют.
- **Вывод:** в UI задача 10 приземлилась корректно. Дефекта нет.
## Остальной дашборд (backfill-путь, профиль ci)
Всё сходится с данными и здорово:
- KPI 16 054 событий / 445 пользователей / Avg 10.6 / Conversion 7.1%.
- Events over Time ровный за `[00:00, 06:00)`; воронка монотонно убывает.
- «Rows by Layer» — 4 ровных столбика ~16k: события не теряются на
переходах STG->ODS->DDS->DM (наглядный контроль целостности).
- Связанные фильтры между визами работают (проверил менти глазами).
## F3. Пульт `generator_control` неудобен для менти и не годится в расписание (DESIGN)
- **Наблюдение менти:** `next-day` спрятан в универсальном DAG с дефолтом
`backfill` и тяжёлой формой; `expected_t_end` для менти — лишнее
усложнение (на автосхеме мира границу знать неоткуда).
- **Предложение менти:** сделать `next-day` отдельным DAG, который
«достаточно триггернуть, ничего не нажимая лишнего».
- **Почему важно:** это прямой вход в задачу про расписание. У планового
запуска не должно быть формы с обязательными полями и дефолта `backfill`
(см. постановку про расписание). Отдельный беспараметрный DAG `next-day`
решает и удобство менти, и пригодность к `schedule`.
- **Связка:** усиливает F1 (форма требует необязательные поля) — общий
корень в том, что один DAG обслуживает и ручной backfill, и то, что
хочется автоматизировать.
## F4. Airflow Grid: Auto-refresh Error (JS)
- **Симптом:** всплывающая ошибка `can't access property "find",
p.dagRuns is undefined` при авто-обновлении Grid во время запуска.
- **Оценка:** похоже на известный косметический баг UI Grid (гонка
авто-refresh, пока у DAG ещё нет прогонов). Работе не мешал. Проверить
версию Airflow и известные issue; при подтверждении — низкий приоритет.
## F5. Быстрый разлогин в Airflow И Superset (BUG, корень TBD)
- **Симптом:** обе веб-морды стремительно разлогинивают в рамках сессии.
- **Что исключено:** ротация секрета. `AIRFLOW__WEBSERVER__SECRET_KEY`
фиксирован (литерал по умолчанию, `AIRFLOW_SECRET_KEY` в `.env` не задан),
`SUPERSET_SECRET_KEY` — жёстко зашитый литерал. Значит, «каждый gunicorn
worker подписывает своим ключом» — не причина.
- **Куда копать:** время жизни сессии (Airflow
`session_lifetime_minutes`, Superset `PERMANENT_SESSION_LIFETIME` — в
`configs/superset` не задан), настройки cookie (SameSite/Secure на
localhost с разными портами), окружение браузера.
- **Влияние на менти:** сильно портит опыт — заставляет постоянно
перелогиниваться. Кандидат в отдельную задачу.
## F6. `next-day` собирается долго (~10,6 мин на дне 2), CPU-bound (PERF/DESIGN)
- **Наблюдение менти:** «как долго собирается следующий день… и это на
мощном процессоре».
- **Замер по метаданным Airflow (этот прогон):** задача `run_next_day` =
**638 с (~10,6 мин)** (18:03:26 -> 18:14:04). Соседние задачи мелкие:
`precheck_next_day` 15 с, `trigger_etl` (ETL full-refresh по 110k
событий) 30 с, `check_after_etl` 6 с. Узкое место — именно генерация
плюс накопительный пересчёт внутри `run_next_day`, не ETL.
- **Три причины:**
1. модельный день = 24 ч против 6 ч у backfill -> ~вчетверо больше
событий за прогон;
2. генерация — однопоточный Python, CPU-bound: много ядер не помогают,
упор в скорость одного ядра;
3. задокументированный накопительный пересчёт по всей истории Kafka
растёт с числом дней (на дне 2 мал, но копится).
- **Связка с задачей про расписание:** это ровно та причина, по которой
перед включением `schedule` нужно решить retention/формат накопительного
состояния (иначе плановый ежедневный `next-day` — растущий многоминутный
CPU-burn). Менти пощупал стоимость руками. См. F3.
## F7. Опыт менти беден на 6h; «история из файла» упирается в размер (DESIGN)
- **Мысль менти:** 6 часов backfill — мало для опыта; хотели хранить
больше и не тратить время менти на генерацию, а грузить из файла.
- **Состояние:** механизм есть — глагол `import` и runbook
`docs/runbooks/startup-history.md` (задача 07 `done`). Но готового
артефакта в репозитории нет: менти всё равно либо генерирует (медленно,
см. F6), либо ищет «где взять файл».
- **Замер:** артефакт за 6 ч = **49 МБ** (плоский JSON). Экстраполяция:
день ~200 МБ, неделя ~1,4 ГБ, месяц ~6 ГБ. В git такое не кладут.
- **Причина раздутости:** артефакт содержит и `state`, и `raw_topics` —
похоже, тащит сырые сообщения Kafka целиком (~8 МБ на модельный час).
- **Развилка (пересекается с F6/расписанием):**
1. целевой размер демо-мира (день/неделя?);
2. формат/сжатие артефакта (`xz` — см. замер ниже; нужен ли `import`-у
`raw_topics`, или хватит компактного `state`);
3. где хранить раз не git (git-lfs / релизный ассет / внешнее хранилище /
«сгенерировать один раз и закэшировать локально»).
- **Вывод:** «богатый мир из файла» и «растить мир расписанием» — один
общий вопрос: как дёшево хранить и переносить много истории. Решать
вместе. Runbook про размер/хранение сейчас молчит — дополнить.
### Решение по целевому размеру (менти, 2026-07-19, обсуждаемо)
- **Целевой стартовый размер демо-мира — 3 модельных дня.** «Больше
одного, но не слишком далеко». Срок обсуждаем.
- **Почему 3:** периодичность видна от 2 дней; 3 дают чёткий паттерн +
один «средний» день без краевых эффектов; появляется сравнение
день-к-дню в Superset. Размер ~600 МБ плоского JSON, gzip ~3060 МБ.
- **Следствие про профиль:** на `ci` (ровная интенсивность, jitter=0)
три дня будут плоскими и скучными. Суточную волну даёт `daily-wave`.
Эталонный артефакт для менти собирать на `daily-wave`, а не `ci`.
Генерация 3 дней разово мейнтейнером — ок (backfill без сна, минуты),
менти только `import`.
### Замер сжатия (2026-07-19)
- Артефакт 6h: raw 49 МБ. **`xz -9e` → 1,1 МБ (48×), 13,5 с.** gzip -9 →
3,0 МБ (17×). xz почти втрое лучше.
- Пересчёт на 3 дня: raw ~590 МБ → **xz ~13 МБ** — кладётся в обычный git
без lfs.
- Паттерн-образец: `~/sources/airflow-greenplum-solution` — `xz -9` жмёт
сид один раз в `bookings/seed/demo.sql.xz` (в git), на загрузке
`xz -dc | ...` стримит. Переносим один в один: одноразовое медленное
сжатие мейнтейнером, потом простой git.
- Вывод менти: сжимать долго один раз не страшно; когда сид отладим —
артефакт кладём в обычный git.
## F8. Два профиля (`ci`/`daily-wave`) путают; менти нужен один (DESIGN)
- **Наблюдение менти:** планировался один удобный профиль с учебной
ценностью; зачем два — непонятно.
- **Что есть (`generator/src/clickstream_generator/launch.py`):**
- `ci` — 6h, скорость ×1, jitter=0. Профиль **автотестов/CI**: быстрый,
маленький, плоский (суточной волны не видно).
- `daily-wave` — 2d, скорость ×60 (сутки ~24 мин), задача 14
«суточная волна за минуты занятия». **Учебный** профиль с волной.
- **Где протекло:** форма backfill перечисляет профили по алфавиту,
первым идёт `ci` → менти по умолчанию подсовывается тестовый плоский
профиль. Сегодняшний прогон шёл на `ci`, оттого дашборд ровный.
- **Куда вести:** при `import` готового артефакта менти профиль не
выбирает вовсе — выбор профиля уходит мейнтейнеру (сборка артефакта на
учебном профиле) и CI. Технически можно оставить один базовый учебный
профиль (волна, ×60), а CI переопределяет длительность на 6h (backfill
без сна, скорость на генерацию артефакта не влияет). Тогда отдельный
`ci` менти не нужен.
- **Проверить перед слиянием:** не зависит ли live-тест (runtime-seam)
от скорости ×1. Если нет — профили честно схлопываются в один.
## F9. Стоимость `next-day`: параллелить не то, инкремент — то (PERF/DESIGN)
Разбор по коду (`generator/src/clickstream_generator/service.py:_run_next_day`).
- **Три куска стоимости:**
1. Генерация нового дня (строки 545–583) — O(события дня), постоянна,
не растёт.
2. Полная перечитка Kafka (строка 591, `KafkaDataTopicReader.load()` +
`ManifestCounters.add_batch`) — читает все data-топики с начала в
RAM, пересчитывает счётчики/суммы с нуля. **Растёт с историей.**
3. ETL `full_refresh` (отдельный DAG) — перестраивает DDS/DM по всему
миру. Тоже растёт.
- **Параллелить — неправильный рычаг:**
- Генерацию нельзя без потери детерминизма (один поток ГПСЧ,
переходящие сессии, шагающее модельное время; воспроизводимость по
seed — базовая ценность). И она не растёт.
- Перечитку можно раскидать по потокам, но это лишь постоянный
множитель. Корень — O(N²) по N дням (день k перечитывает k дней).
- **Оптимизировать — правильный рычаг:**
- Счётчики инкрементальные: хранить накопленное состояние
`ManifestCounters` в manifest/state, добавлять только новый день
(его `sent_counts` уже посчитаны при генерации). O(N²) -> O(N).
- Убирает и рост RAM: сейчас `reader.load()` держит всю историю в
памяти (3 дня ~неск. ГБ распарсенного JSON — риск OOM).
- Формат: уникальность (`click_ids`/`user_ids`) сейчас через `set` по
всей истории — перенести множества в state; контрольную сумму
сделать катящейся (комбинировать посуточные), не хэш всего заново.
- **Связка:** это и есть решение «новый формат накопительного состояния
manifest», которое runbook называет обязательным перед расписанием
(см. F6). Оптимизация = сердцевина retention-задачи, не отдельная.
- **Режимы:** разовая сборка артефакта — медленно не страшно;
плановый ежедневный `next-day` — инкремент обязателен (через месяц
каждый запуск перечитывал бы 30 дней).
+22
View File
@@ -90,6 +90,28 @@ user_domain_id (пользователь, постоянный)
Механизм реализован через Airflow DAG `generator_control` и служебный чистый Механизм реализован через Airflow DAG `generator_control` и служебный чистый
путь `make generated-history-analytics`; решение про часы — [ADR-0005](./docs/adr/0005-generator-model-clock.md). путь `make generated-history-analytics`; решение про часы — [ADR-0005](./docs/adr/0005-generator-model-clock.md).
### Мир (стенда)
Совокупное состояние данных стенда: события в Kafka, слои ClickHouse,
состояние генератора (модельные часы, живая популяция) и manifest с
контрольными числами. «Вырастить мир» — добавить в него модельное время
(`next-day` или живой поток), «восстановить мир» — залить его из артефакта.
### Эталонный мир (reference world)
Канонический стартовый мир менти: 3 модельных дня на профиле `daily-wave`,
собран мейнтейнером один раз, хранится сжатым артефактом в git. Все менти
импортируют один и тот же файл — числа в лабах воспроизводимы
число-в-число. Решение — [спека редизайна пути менти](./docs/specs/2026-07-19-mentee-path-redesign.md).
### Три режима менти
`import` (база: загрузить эталонный мир) и две ветки от восстановленного
состояния: `next-day` (пакетно добавить модельный день) и `continue`
(живой поток от той же границы). Это разные педагогики и разные лабы;
`backfill` — не режим менти, а инструмент мейнтейнера для сборки
эталонного мира. См. ту же спеку.
### Модельное время и масштаб (×K) ### Модельное время и масштаб (×K)
**Модельное время стенда** отвязано от настенных часов: генератор крутит **Модельное время стенда** отвязано от настенных часов: генератор крутит
@@ -0,0 +1,155 @@
# Редизайн пути менти: мир из артефакта и две ветки роста
Статус: принято, в работе (ветка `feature/mentee-path`).
Источник: ручной HITL-прогон пути менти 2026-07-19 — находки F1F9
(рабочая копия — `.scratch/hitl-findings.md`; постоянный след — история git,
срез `0e312b3`). Решения приняты в обсуждении с пользователем 2026-07-19.
## Проблема
Сегодняшний первый шаг менти — `backfill`: генерация истории с нуля.
Это медленно (день модельной истории — минуты и десятки минут CPU),
а результат беден (6 часов плоского профиля `ci`). Вокруг — ворох трения:
- пульт `generator_control` подсовывает `backfill` дефолтом и носит имя,
непонятное осваивающему стенд;
- два профиля (`ci`/`daily-wave`) путают: менти достаётся плоский тестовый;
- `next-day` спрятан в общем пульте и не годится для расписания:
каждый прогон перечитывает всю историю Kafka заново — стоимость растёт
квадратично по дням, память — линейно (риск OOM);
- готового артефакта в репозитории нет: механизм `import` есть, а
импортировать нечего.
## Цели
- Менти получает богатый мир за секунды-минуты, а не генерирует его.
- Путь менти проходится в Airflow UI пустыми формами, без справочника.
- У всех менти одинаковый мир: числа в лабах воспроизводимы число-в-число.
- Мир можно растить двумя осознанными способами: пакетно по дням и живым
потоком.
- Плановый `next-day` перестаёт дорожать с возрастом мира.
## Целевая модель
**Основной режим менти — `import` эталонного мира.** От восстановленного
состояния расходятся две ветки — это три режима менти (термины — в
`CONTEXT.md`):
- **База: `import`** — загрузить эталонный мир из артефакта в git.
- **Ветка A: `next-day`** — пакетно добавить следующий модельный день.
- **Ветка B: `continue`** — живой поток от той же границы (скорость ×60).
`backfill` уходит мейнтейнеру: это инструмент сборки эталонного мира,
не первый шаг менти (аналогия из соседнего курса Airflow: по умолчанию
загружают готовый первый месяц, а не собирают его генератором).
### Путь менти (UX)
1. `make up` — поднять стенд (единственная команда терминала).
2. Airflow UI: `ddl_init` — создать схему.
3. Airflow UI: `world_init` — Trigger с пустой формой. Дефолтная операция —
`import`, путь к артефакту — дефолтом на эталонный мир. DAG сам заливает
Kafka, запускает ETL и сверяет витрины.
4. Superset — готовый трёхдневный мир.
5. Дальше по выбору лабы: `world_next_day` (кнопка без параметров) или
`make generator-continue` (живой поток).
### Решения
1. **Эталонный мир**: 3 модельных дня на профиле `daily-wave` (суточная
волна видна, есть «средний» день и сравнение день-к-дню). Собирает
мейнтейнер один раз через `backfill`; хранится в git по фиксированному
пути `data/startup_history/` сжатым `xz` (~13 МБ; замер 2026-07-19:
`xz -9e` жмёт артефакт в 48 раз). Формат не меняется: `import` требует
`raw_topics`, сжатие снимает вопрос размера. Паттерн-образец —
`airflow-greenplum-solution` (сид `demo.sql.xz` в git, стрим-распаковка
при загрузке).
2. **Пульт переименовывается: `generator_control``world_init`.**
Дефолт формы — `import` (сейчас `backfill` — корень находки F3);
`backfill` и `check` остаются в выпадашке, но уходят из учебных
инструкций в runbook мейнтейнера. Список DAG'ов читается лесенкой:
`ddl_init``world_init``world_next_day`. Цена: история прогонов
старого dag_id теряется (на учебном стенде не жалко), доки и
контрактный тест правятся синхронно.
3. **`world_next_day` — отдельный беспараметрный DAG**: триггернул — день
добавился. `schedule` прописан, но DAG paused и `catchup=False`:
процесс тяжёлый, автозапуск включается осознанно (шаг лабы), живой
рост мира и так даёт ветка `continue`.
4. **Инкрементальные счётчики manifest — предусловие расписания.**
Накопительное состояние счётчиков (суммы, множества uid/click_id,
катящаяся контрольная сумма) переезжает в state/manifest; новый день
только добавляется. Полная перечитка Kafka уходит: стоимость дня
становится ~постоянной (сейчас день 2 — 638 с и растёт), риск OOM
снимается. Разбор по коду — hitl-findings, F9.
5. **Один учебный профиль**: `daily-wave` (волна, ×60). `ci` (×1, плоско,
6h) — служебный для автотестов, менти не предлагается. Перед слиянием
проверить, что runtime-seam-тест не завязан на скорость ×1.
## Чего здесь не делаем
- **Не параллелим генерацию** — ломает детерминизм (один поток ГПСЧ,
переходящие визиты); корень стоимости не в ней, а в перечитке (F9).
- **Не трогаем гео-фактуру** — отдельная спека по ADR-0006.
- **Не мигрируем лабы курса** (`docs/course/`) — они отстали от реальности
и потребуют редизайна под три режима; отдельная работа, зафиксирована
issue в трекере.
- **Не чиним здесь F5** (быстрый разлогин Airflow/Superset) **и F4**
(JS-ошибка Grid) — отдельные issues вне фичи.
- **Не включаем расписание по умолчанию** и не занимаемся retention сверх
инкрементальных счётчиков.
## Форма работ
Четыре дочерних issue (GitHub, по контракту `docs/agents/issue-tracker.md`;
корневой issue ссылается сюда):
1. **Эталонный мир**: сборка 3-дневного артефакта, `xz`-хранение в git,
стрим-распаковка на `import`, дефолтный путь в форме.
2. **Поверхность DAG'ов**: переименование в `world_init`, дефолт `import`,
вынос `world_next_day` беспараметрным DAG'ом с paused-расписанием,
синхронная правка доков и контрактных тестов.
3. **Инкрементальные счётчики manifest** (без них расписание не включать).
4. **Один учебный профиль**: `daily-wave` — учебный дефолт, `ci`
служебный; проверка независимости runtime-seam-теста от ×1.
Зависимости: (1) и (4) сцеплены (эталонный мир собирается на `daily-wave`);
(3) блокирует включение расписания из (2), но не сам DAG.
## Проверка
- Чистый стенд: путь менти из раздела UX проходится пустыми формами;
от `make up` до дашборда — без документации и без терминала (кроме
`make up`).
- `import` эталонного мира — секунды-минуты, не десятки минут.
- После `import` у любого менти совпадают manifest-числа эталонного мира
(события, визиты, пользователи, контрольная сумма).
- `world_next_day` на дне N по времени ~равен дню 1;
`make generated-history-chain-check` зелёный на стыках.
- `make test` / `make lint` зелёные; профиль `ci` продолжает обслуживать
автотесты.
## Решения и отклонённые варианты
- **Без артефакта, просто ускорить backfill** — отклонено: время менти и
воспроизводимость (общий файл надёжнее «одинаковой генерации у всех»).
- **Мир «из коробки» при `make up`** — отклонено: механизм становится
невидимым, магия в учебном стенде мстит; явный `import` — учебная
ценность и общее начало обеих лаб.
- **Расписание включённым по умолчанию** — отклонено: эпизодический стенд,
тяжёлый процесс, сюрпризы catchup.
- **Три тонких DAG вместо пульта** — отклонено: плодит сущности; пульт с
правильным дефолтом + один тонкий `world_next_day` достаточно.
- **git-lfs / релизные ассеты для артефакта** — отклонено: после `xz`
артефакт помещается в обычный git.
## Влияние на документацию
- `README.md`, `docs/OPERATIONS.md`, `docs/ARCHITECTURE.md`,
`docs/REPO_MAP.md` — новый путь менти и имена DAG'ов (в тех же PR, что
и изменения).
- `docs/runbooks/startup-history.md``backfill`/сборка эталонного мира
переезжают сюда как процедура мейнтейнера; дополнить про размер и
хранение.
- `CONTEXT.md` — термины «мир (стенда)», «эталонный мир», «три режима
менти» (добавлены этой же спекой).
- Лабы `docs/course/` — сознательно не здесь (см. «Чего не делаем»).