docs(generator): находки ручного HITL пути менти и handoff
- Зачем:
- долг ручной HITL-проверки пути менти переносился пятый handoff подряд;
проверка вскрыла набор находок и наметила редизайн пути менти.
- Что:
- добавлены находки HITL (F1-F9) с рамочной моделью трёх режимов менти:
import-база, ветки next-day и continue, под каждую — свой урок/лаба.
- зафиксированы решения: 3-дневный артефакт на daily-wave, сжатие xz,
один учебный профиль, беспараметрный next-day, инкрементальные счётчики.
- добавлен handoff по ADR-0003 с планом раскладки находок в постановки.
- Проверка:
- чтение .scratch/generator-model-time-startup-history/hitl-findings.md
и .scratch/handoffs/20260719-2144-hitl-mentee-path-redesign.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 ~30–60 МБ.
|
||||
- **Следствие про профиль:** на `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 дней).
|
||||
@@ -0,0 +1,103 @@
|
||||
# Handoff: ручной HITL пути менти пройден — вырос в набросок редизайна
|
||||
|
||||
Дата: 2026-07-19 21:44.
|
||||
Жанр: handoff по ADR-0003 после сессии «ручная HITL-проверка пути менти»
|
||||
(долг переносился пятый handoff подряд — закрыт).
|
||||
|
||||
## Что сделано
|
||||
|
||||
- **HITL-проверка пути менти пройдена целиком, своими руками через UI.**
|
||||
Чистый стенд (`make clean` + `make up`) -> Airflow UI: `ddl_init` ->
|
||||
`generator_control` `backfill` (профиль `ci`) -> ETL -> `make superset-init`
|
||||
-> дашборд глазами -> `generator_control` `next-day` -> проверка стыка.
|
||||
Каждый шаг сверен по данным (ClickHouse, manifest, chain-check).
|
||||
- **Результат мира:** backfill `[00:00, 06:00)` = 16 054 события; после
|
||||
`next-day` = 110 602 события, визитов 10 228, пользователей 1 726;
|
||||
`boundaries` = три границы, `model_t_end` = `2026-01-02 06:00`.
|
||||
`generated-history-chain-check` — EXIT 0 (стык 06:00: 9 переходящих
|
||||
визитов, непарных 0, однородность подтверждена).
|
||||
- **9 находок + рамочная модель** — в
|
||||
`.scratch/generator-model-time-startup-history/hitl-findings.md`.
|
||||
Половина находок родилась из наблюдений менти. HITL из «проверить, что
|
||||
не сломалось» перерос в **набросок редизайна пути менти**.
|
||||
|
||||
## Ключевые выводы (кратко; подробно — в hitl-findings.md)
|
||||
|
||||
- **Рамочная модель менти:** основной режим — `import` артефакта (база),
|
||||
дальше две ветки от одного состояния: `next-day` (пакетно) и `continue`
|
||||
(живой поток). Это три разных лабораторных. `next-day` и `continue`
|
||||
не схлопывать. Подтверждено `launch.py:117-137`.
|
||||
- **F7 + замер:** опыт на 6h беден; нужен готовый артефакт из файла.
|
||||
Артефакт 6h = 49 МБ; `xz -9e` -> 1,1 МБ (48x, 13,5 с). На 3 дня ~13 МБ
|
||||
— кладётся в обычный git. Паттерн-образец: `airflow-greenplum-solution`
|
||||
(`bookings/seed/demo.sql.xz`).
|
||||
- **Решение менти:** целевой стартовый мир — **3 дня на `daily-wave`**
|
||||
(обсуждаемо). На `ci` три дня плоские — волну даёт `daily-wave`.
|
||||
- **F8:** два профиля путают. Свести к одному учебному (волна, ×60);
|
||||
`ci` (×1) — служебный для тестов. ×60 оправдан live-лабой (`continue`).
|
||||
- **F9:** `next-day` растёт O(N²) из-за полной перечитки Kafka для
|
||||
счётчиков (`service.py:591`). Параллелить не то (ломает детерминизм);
|
||||
правильно — инкрементальные счётчики (O(N), снимает риск OOM). Это и
|
||||
есть решение «новый формат состояния manifest», блокирующее расписание.
|
||||
- **F1, F5 (баги UI):** форма `generator_control` держит необязательные
|
||||
поля обязательными (`type="string"` без `null`); Airflow и Superset
|
||||
быстро разлогинивают (ротацию секрета исключил, корень TBD).
|
||||
|
||||
## Состояние
|
||||
|
||||
- Ветка `feature/data-generator`. После этого handoff в дереве останутся:
|
||||
`hitl-findings.md` + этот handoff (коммитятся), плюс **не коммитить**
|
||||
`data/ci_backfill.json` (49 МБ, побочный от backfill; `.gitignore` его
|
||||
не покрывает — риск, см. ниже).
|
||||
- Стенд **оставлен поднятым** по просьбе пользователя; в мире 1,25 суток
|
||||
(профиль `ci`), live-генератор не запущен. Выросший мир можно смотреть
|
||||
в Superset (dashboard id 1).
|
||||
|
||||
## Что дальше
|
||||
|
||||
1. **Разложить находки в постановки по плейбуку** (этапы 1-2, двойное
|
||||
слепое ревью). Ориентировочно ~4 задачи (номера 21+ в `issues/`):
|
||||
- **Ядро:** эталонный 3-дневный артефакт (`daily-wave`) как база
|
||||
менти через `import`; формат/сжатие (`xz`, нужен ли `raw_topics`);
|
||||
место хранения (git после отладки сида). Снимает F7.
|
||||
- **Профиль:** один учебный профиль, `ci` -> служебный тестовый.
|
||||
Перед слиянием проверить, не зависит ли live-тест от скорости ×1
|
||||
(F8).
|
||||
- **Расписание + retention:** беспараметрный DAG `next-day` (F1/F3),
|
||||
`schedule`, инкрементальные счётчики manifest (F9/F6). Это одна
|
||||
связка — их корень общий.
|
||||
- **Баг-фиксы:** F1 (форма) и F5 (разлогин); F4 (JS-баг Grid) низкий.
|
||||
2. **Три режима -> свои уроки/лабы** — вход в учебный контент
|
||||
(`docs/course/`). Под каждую ветку отдельная лаба: `next-day`
|
||||
(пакетная инкрементальная обработка) и `continue` (потоковый приём);
|
||||
общая база `import` — их совместное начало. Отдельно от
|
||||
инфраструктурных задач.
|
||||
3. **Мелочь по гигиене:** `data/*.json` не покрыт `.gitignore` —
|
||||
побочные артефакты backfill (49 МБ) рискуют попасть в коммит. Решить:
|
||||
правило в `.gitignore` или чистить артефакты после прогонов.
|
||||
|
||||
## Проверки этой сессии
|
||||
|
||||
- `ddl_init`: 4 слоя созданы (stg 12 / ods 8 / dds 2 / dm 6 таблиц).
|
||||
- Форма мира после backfill: пирамида 445 < 1516 < 16 054, интервал
|
||||
`[00:00, 06:00)` точный.
|
||||
- После `next-day`: 3 границы в manifest, мир до `2026-01-02 06:00`.
|
||||
- `generated-history-chain-check`: EXIT 0.
|
||||
- Длительность `next-day` (метаданные Airflow): задача `run_next_day`
|
||||
638 с (~10,6 мин); ETL `trigger_etl` 30 с — узкое место в генерации.
|
||||
- Замер сжатия: `xz -9e` 48x за 13,5 с; gzip -9 17x.
|
||||
|
||||
## Отклонения процесса
|
||||
|
||||
- Ревью субагентами не запускали: пользователь просил «свежий взгляд»,
|
||||
что по договорённости = без субагентов. Проверка проведена мной руками.
|
||||
- Обход бага F1: форму backfill/next-day заполняли значениями профиля
|
||||
`ci` целиком (пустые необязательные поля UI не пропускает).
|
||||
|
||||
## Suggested skills
|
||||
|
||||
- `claude-subagent-playbook` — разложить находки в постановки тем же
|
||||
конвейером (этапы 1-2 с нуля).
|
||||
- `conventional-commits` — коммиты.
|
||||
- `research` / Context7 — для задачи про профиль/форму (Airflow Param
|
||||
`type=["null","string"]`) и про формат артефакта.
|
||||
Reference in New Issue
Block a user