Notice — всё, что Iva говорит сама (CONTEXT.md). Решено развести два вида и судить их
разными правилами. Report (плановая сводка: ночные отчёты памяти, утренний дайджест)
по умолчанию выключен и включается тумблером в /menu → Уведомления. Alert (проблема
или требуемое действие: алерты brain, предложение обновиться) не выключается — но взамен обязан
говорить, что сломалось, чем это грозит и что сделать, и повторяться не чаще раза в неделю
на одну и ту же проблему; смена существа проблемы и возврат после починки говорят сразу.
Язык любого 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 не спамит (тот же выбор, что «ложный ✅ хуже спиннера» в апдейтере).