# 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)
(форма доработки).

> Обновление (2026-06-14): тезис «генератор сосуществует с сидом, не заменяет
> его» частично пересмотрен в [ADR-0006](./0006-generation-as-sole-analytics-source.md)
> — для аналитического контура стенда генерация заменяет статический сид; сид
> выводится из оборота (целевым образом — полностью). Сосуществование остаётся
> верным лишь для переходной роли сида (палитра атрибутов / dev-фикстура).

## Решение

Режим `steady-stream` (живой поток на стенде) питается **синтетическим
генератором, переписанным с нуля по иерархической модели** «популяция
пользователей → сессии → события», а **не реплеем статического сида**. Модель
интенсивности текущего генератора (Poisson по тикам + дневной коэффициент +
jitter) сохраняется. Режим `bootstrap` (статический сид `data/*.jsonl`) и уроки
0–6 остаются прежними — генератор **сосуществует** с сидом, не заменяет его.

## Контекст

Проект сменил назначение на учебный стенд с курсом. Базовый курс стоит на
статическом сиде, и это осознанно: сид **честен внутри визита** (`click_id`
группирует несколько событий — настоящая воронка; по полному замеру 2026-06-10 —
1..27 событий, медиана 10, см. профиль сид-датасета в `CONTEXT.md`). Но у сида
два потолка, которые
сид принципиально не закрывает:

- **`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-спеку** и здесь
  намеренно не фиксируется.
