# Report'ы выключены по умолчанию, Alert'ы обязаны быть полезными

Notice — всё, что Iva говорит сама (CONTEXT.md). Решено развести два вида и судить их
разными правилами. **Report** (плановая сводка: ночные отчёты памяти, утренний дайджест)
по умолчанию выключен и включается тумблером в `/menu` → Уведомления. **Alert** (проблема
или требуемое действие: алерты brain, предложение обновиться) не выключается — но взамен обязан
говорить, что сломалось, чем это грозит и что сделать, и повторяться не чаще раза в неделю
на одну и ту же проблему; смена существа проблемы и возврат после починки говорят сразу.
Предложение бета-обновления (ADR-0017) ключуется собираемой сборкой: новая сборка — новое
существо, та же сборка повторно — не чаще раза в неделю.
Язык любого Notice — один, тот же резолвер, что у чата (`settings.language` → `AGENT_LANGUAGE`
→ ru): половина сообщения по-английски читается как поломка.

Обоснование. Ночной отчёт памяти уходил в чат с первой установки и никто его не просил:
пользователь получал утренний текст про SUPERSEDE и card operations, а выключателя не было
вовсе. Это нарушает ADR-0001 («работает из коробки как продукт»): чужой self-hoster получает
непрошеное сообщение каждое утро и не знает, что с ним делать. Обратная крайность — сделать
отключаемым всё — отвергнута: алерт о том, что память не бэкапится, ценен ровно тогда,
когда её выключил бы уставший от шума человек, и молчание тут стоит данных (ADR-0002).
Компромисс — цена, которую платит Alert: раз в неделю, с конкретной командой в тексте.

Отвергнутые альтернативы. «Слать Report всегда, но покороче» — не решает главного: сообщение
всё равно непрошеное, а планка «продукт» требует согласия, а не терпения. «Дать каждому
Alert свой тумблер» — рождает установку, где выключено всё, и владелец узнаёт о потерянной
памяти через месяц. «Слать Alert каждую ночь, пока проблема жива» — шум, из которого
вырабатывается привычка не читать; недельный ритм оставляет алерт заметным.

Дроссель детерминированный: `data/alert-state.json`, ключ проблемы → существо и время
последней отправки, запись атомарная. Битое или отсутствующее состояние трактуется как
«слать» (fail-open): пропущенный алерт дороже увиденного дважды. Неотправленный алерт
состояние не пишет — иначе один отказ Telegram глушил бы проблему на неделю. Пишется состояние
своим кодом на `node:fs`, без `agent/`: дроссель нужнее всего установке, у которой это дерево
как раз и сломано.

Плановый ход, результат которого доставляет код (ночная сборка памяти и утренний дайджест
по расписанию), не отправляет ничего сам. Красная строка `agent/instructions.md` требует для
любого отчёта rich message, поэтому в ней записано исключение ровно для этих двух ходов:
иначе системная инструкция и промпт хода спорят, побеждает более громкая, и владелец получает
два сообщения вместо одного. Дайджест, запрошенный в чате, — обычный ход, правило действует.

Миграция одноразовая: установка, которая раньше получала утренний отчёт, один раз слышит,
почему утро замолчало, и где включить обратно. Свежая установка не слышит ничего — ей нечего
терять, а первое сообщение агента не должно быть про настройку, которую пользователь не
включал. Решение принимается на ПЕРВОМ прогоне, где маркера нет, и записывается в сам маркер:
поздние ночи вопрос не пересматривают, чем бы ни выглядела установка к тому времени. Владелец,
трогавший тумблер (ключ `memoryReports` в settings пишет только экран `/menu`), получает
«not-needed»: рассказывать ему про его же действие — нотация. Установка без настроенного чата
тоже закрывает вопрос молча: пока чат появится, новость протухнет.

Признак «отчёты у этой установки были» — **best-effort, а не гарантия**. Считаются следы
ЗАВЕРШЁННОГО прогона: курсор сессии любого периода в `data/`, запись периода в
`data/rollup-status.json` с полем завершения (`lastFinishedAt`/`lastSuccessAt`), дневные
сводки в `vault/summaries/daily/`. Поле завершения обязательно: спавнер расписаний резервирует
слот в том же файле ДО запуска, и по одному имени периода самый первый прогон принял бы
собственную бронь за чужой прошлый успех. Курсора одного мало — его сносит уборка зависшего
хода; сводок одних мало — vault может быть не на месте, поэтому берётся дизъюнкция. Установка
≤0.3.9 без vault под рукой следов не оставит и Notice не получит: отчёты просто замолчат,
объяснение — в CHANGELOG. Цена ошибки в эту сторону — молчание вместо объяснения; в обратную —
нотация человеку, который отчётов не видел.

Заявка на Notice подаётся ДО отправки (атомарный `O_EXCL`), поэтому крэш между заявкой и
доставкой теряет Notice навсегда. Обратный порядок дал бы дубль на гонке daily и weekly, а
дубль хуже потери: пропавший Notice стоит строки в доке, повторяющийся — доверия к тому, что
Iva не спамит (тот же выбор, что «ложный ✅ хуже спиннера» в апдейтере).

Дополнение 2026-10-26. Узкое исключение для дней рождения Google Контактов в Brief и Watch описано в ADR-0020: подтверждение в seen следует за доставкой конкретной части. Остальные Notice сохраняют действующий порядок заявки.

Дополнение 2026-09-01. Исключение для класса «потерян вход владельца»
(`telegram-acceptance:<chat>` в `scripts/poller/routing.ts`): входящее сообщение не дошло
до модели. Такой Alert повторяется раз в 10 минут, а не раз в неделю. Потерянный вход —
прямой ущерб, не шум; неделя молчания выглядит как мёртвый бот. Все остальные Alert
оставляют недельный период без изменений.

Дополнение 2026-10-23. Alert о протухшем входе в модель (`model-auth:<провайдер>`)
повторяется раз в сутки. Без входа Ива теряет ответы и ходы по расписанию, включая
Watch и Brief. Отправляет код без модели, после тихих часов; успешный вызов модели
после доставленного Alert один раз сообщает о восстановлении и снимает дроссель.
В `alert-state.json` ключ `model-auth-fact:<провайдер>` хранит факты вызовов в строке
`essence`, а `lastSentAt` равен 0. Старый выпуск сохраняет этот
неизвестный ключ дросселя и пропускает его при откате. Писатель этих
ключей и отправитель один, процесс агента: он последовательно обрабатывает события
всех ходов и минутный тик. Дочерний `scripts/model-auth-alert.ts` выполняет только
транспорт Telegram, состояния у него нет. Существующий дроссель Notice вынесен
в `packages/alert-state/index.ts`, общий с операционными скриптами. Скрипты сохраняют
прежний недельный дроссель и атомарную запись даже на установке без `agent/`.
Агент получает обязательный замок через статический импорт authored-кода;
при отказе замка запись отклоняется. Ответ на упавший ход отправляется в исходный
чат до записи состояния, в том числе в тихие часы. Доставленный личный ответ
владельцу засчитывается как Alert. Doctor читает факты и использует общий текст.
