docs(generator): зафиксировано направление переработки генератора

- Зачем:
  - при возврате к генератору не переоткрывать выбор «генератор vs реплей»
    и иметь готовую рамку требований под реализацию steady-stream.
- Что:
  - ADR-0004: steady-stream питается синтетическим иерархическим генератором,
    не реплеем сида (обоснование + отклонённые варианты C/B).
  - спека docs/specs/2026-06-09: требования к иерархической модели сущностей
    и критерии приёмки; математика делегирована follow-up-спеке.
  - CONTEXT.md: термины «популяция пользователей», «возвращающийся пользователь».
- Проверка:
  - прочитать ADR-0004 и спеку; сверить термины в CONTEXT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dmitry Dementiev
2026-06-09 18:04:00 +03:00
co-authored by Claude Opus 4.8
parent dd2c3cdaf9
commit 6ddc7a90d4
3 changed files with 270 additions and 0 deletions
@@ -0,0 +1,78 @@
# ADR-0004: Steady-stream источник — синтетический иерархический генератор, не реплей
Принято: 2026-06-09
Статус: accepted
Связано: [`generator/KNOWN_ISSUES.md`](../../generator/KNOWN_ISSUES.md) (диагноз
дефекта), [`CONTEXT.md`](../../CONTEXT.md),
[ADR-0002](./0002-specs-as-durable-design-docs.md) (спеки как durable design-доки),
спека [`docs/specs/2026-06-09-generator-rework-hierarchical.md`](../specs/2026-06-09-generator-rework-hierarchical.md)
(форма доработки).
## Решение
Режим `steady-stream` (живой поток на стенде) питается **синтетическим
генератором, переписанным с нуля по иерархической модели** «популяция
пользователей → сессии → события», а **не реплеем статического сида**. Модель
интенсивности текущего генератора (Poisson по тикам + дневной коэффициент +
jitter) сохраняется. Режим `bootstrap` (статический сид `data/*.jsonl`) и уроки
0–6 остаются прежними — генератор **сосуществует** с сидом, не заменяет его.
## Контекст
Проект сменил назначение на учебный стенд с курсом. Базовый курс стоит на
статическом сиде, и это осознанно: сид **честен внутри визита** (`click_id`
группирует 1..7 событий — настоящая воронка). Но у сида два потолка, которые
сид принципиально не закрывает:
- **`users == sessions`** — каждый пользователь имеет ровно один `click_id` (1:1),
возвращающихся пользователей нет (см. `CONTEXT.md`).
- **одноразовость** — сид заливается батчем; не видно, как ClickHouse и витрины
ведут себя на непрерывном живом потоке.
Нужен режим, в котором стенд **живёт и движется** на правдоподобных данных
(воронка + жизнеподобные колебания интенсивности), при **скромном объёме** (у
менти может не быть мощного железа — на объём/стресс не закладываемся).
Двойная учебная ценность — ключевой критерий выбора: ценны не только *данные*
(живой стенд + честная пирамида `users < sessions < events`), но и **сам
генератор как объект изучения** — его генеративная модель достойна того, чтобы её
разбирать в курсе.
Текущая реализация генератора не дорабатывается инкрементально: у неё
концептуальный дефект модели сущностей (свежий `click_id` на каждое событие
схлопывает иерархию — см. `generator/KNOWN_ISSUES.md`), переписываем с нуля.
## Рассмотренные варианты
- **A — синтетический иерархический генератор (принято).** Популяция юзеров с
постоянным `user_domain_id` → 1..N сессий (`click_id` на сессию) → 1..M
упорядоченных по времени событий с правдоподобной воронкой. Единственный
вариант, дающий возвращающихся пользователей (`users < sessions < events`) и
учебную ценность самого моделирования. Цена — самый большой объём работы и
риск ошибиться в статистической модели.
- **C — реплей честного сида на часах.** Лить реальные записи сида в Kafka во
времени, переписывая `event_timestamp` в «сейчас», зацикливая пул и модулируя
rate. **Отклонено.** Дёшев и даёт гарантированно честную воронку почти без
риска, **но**: (1) обходит ровно ту генеративно-модельную часть, ради учебной
ценности которой всё и затевается; (2) наследует вырождение `users == sessions`
из сида — полную пирамиду не даёт никогда; (3) «бесконечность» = зацикленный
конечный пул.
- **B — минимальный «живой» генератор с грубой воронкой.** Тот же rewrite, но
воронка на фиксированных вероятностях, без глубины. Отклонено как
половинчатое: числа воронки менее убедительны, а вопрос возвратов всё равно
надо решать — то есть основной сложности не избегает.
## Последствия
- Режим `steady-stream` фиксируется как синтетическая генерация; направление
переоткрывать не нужно (типовой вопрос «почему не реплей?» закрыт здесь).
- `bootstrap`-сид и уроки 0–6 не трогаем; живой поток подаётся отдельным уроком 7.
- Контракт данных потока: здоровая пирамида `users < sessions < events`,
монотонное время внутри сессии, `click_id` переиспользуется внутри сессии.
Этот контракт наследуют будущие артефакты (спека доработки, урок 7, возможные
правки витрин).
- Детальная архитектура/требования — в спеке `2026-06-09-generator-rework-hierarchical.md`.
- **Статистическая модель** (распределения событий/сессия, сессий/пользователь,
межсессионные паузы; нужен ли настоящий session-timeout; персистентность
популяции через рестарты) **выносится в отдельную follow-up-спеку** и здесь
намеренно не фиксируется.