## Критерии приёмки

Постановка пришла текстом и критериев не назвала. Ниже — **предложенные**;
данными они становятся после ответа на чекпоинте.

- В секции `[telegram]` файла настроек есть ключ `enabled`, и он один решает,
  поднимается ли вход. Ключ доступа второго значения не несёт.
- Файл настроек с `enabled = false` даёт подъём одним входом, к Telegram не
  уходит ни одного обращения, а в журнале ровно одна запись уровня `INFO`.
- Файл настроек с `enabled = true` и пустым `bot_token` роняет старт; сообщение
  называет имя ключа и не содержит его значения.
- Файл настроек без ключа `enabled` негоден: загрузка кончается отказом, и
  сообщение называет недостающий ключ. Умолчания у признака нет.
- Прежние правила при включённом входе сохранены: отказ Bot API роняет старт,
  недоступность Telegram даёт подъём с записью уровня `WARN`.
- Признак поднятости входа Telegram выставляется во всех случаях, включая
  выключенный.
- `task gate` зелёный.

Ниже — рубрика ревью дизайна, теми же критериями. Пункты, целиком совпавшие с
перечнем выше, не повторяются.

- **Таблица режимов полна.** Для каждой комбинации «признак задан или нет ×
  признак истинен или ложен × ключ доступа пуст, непуст или подсказка» назван
  ровно один исход из трёх: подъём с ботом, подъём без бота, отказ старта.
- **Опечатка не выключает вход молча.** `enable`, `Enabled`, ключ в чужой
  секции, отсутствующая секция — каждый случай даёт отказ, а не тихий выбор
  режима по нулевому значению.
- **Проверка целиком предшествует необратимому.** Приговор о настройках выносится
  до открытия порта, до применения шагов схемы и до создания каталогов.
- **Выбранный режим наблюдаем, и наблюдаемость различает основания.** Из журнала
  и метрик видно и «работает ли вход сейчас», и «по какому основанию он не
  поднят»: выбор владельца, ошибка настройки, недоступность собеседника.
- **Решение о режиме принимается один раз и в одном месте.** Порядок «умолчания
  → файл → приговор» зафиксирован; ни один потребитель не пересчитывает
  «поднят ли вход» из полей настроек самостоятельно.
- **Виды отказа различимы по сообщению:** файла нет, файл не разбирается, ключ
  не задан, ключ задан негодно — по каждому видно, что чинить, и код выхода
  ненулевой.
- **Выключенный вход не оставляет хвостов.** Клиент не заводится, сетевого
  обращения нет, остановка не ждёт несуществующего собеседника.
- **Обратная совместимость файла названа в обе стороны** — что делает новый код
  со старым файлом и старый код с новым, вместе с порядком выкладки и условиями
  отката.
- **Отсутствие умолчания объявлено там, где записана конвенция**, а не только
  комментарием в коде.
- **Отказ разбора файла настроек не несёт содержимого файла.** Поломанная строка
  секретного ключа даёт отказ с номером строки и именем ключа, но без единой
  подстроки значения. Несовпадение типов при этом по-прежнему называет ключ и
  типы.

## 1. Настройки

- [x] 1.1 Добавить поле `Enabled bool` с тегом `toml:"enabled"` в
      `config.TelegramConfig`; умолчания в `defaultConfig()` для него не заводить
      и объяснить это комментарием
- [x] 1.2 Поднять `MetaData` из `toml.DecodeFile` в `LoadConfig` и отказывать в
      загрузке, когда ключ `telegram.enabled` в файле не задан; сообщение
      называет ключ. `MetaData` наружу из `LoadConfig` не отдавать
- [x] 1.3 Написать `TelegramConfig.Validate()` по образцу `AuthConfig.Validate()`:
      при `Enabled` и пустом `BotToken` вернуть отказ с именем ключа `bot_token`
      и без его значения
- [x] 1.4 Позвать `cfg.Telegram.Validate()` в `main.go` рядом с проверкой
      секции `[auth]`; отказ роняет процесс через `logger.Error` и `os.Exit(1)`
- [x] 1.5 Проверить `TelegramConfig.Validate()` тестами: включён и ключ есть —
      ошибки нет; включён и ключ пуст — ошибка называет `bot_token` и не несёт
      значения; выключен и ключ пуст — ошибки нет
- [x] 1.6 Проверить `LoadConfig` тестом на временном файле: секция `[telegram]`
      без ключа `enabled` даёт отказ с именем ключа; с ключом — загрузка проходит
      и значение доезжает обоими значениями

## 1а. Отказ разбора не несёт содержимого файла

- [x] 1а.1 В `LoadConfig` перестать заворачивать отказ `toml.DecodeFile` через
      `%w`: разобрать его по семействам и собрать сообщение самому
- [x] 1а.2 `toml.ParseError` (через `errors.As`) — взять путь, строку, столбец и
      последний ключ; поле `Message` в сообщение не брать
- [x] 1а.3 Прочие отказы декодера — взять текст как есть: он собран из имён
      ключей и типов. Причину разреза записать комментарием, иначе следующая
      правка сведёт две ветки в одну
- [x] 1а.4 Проверить тестом на временном файле: строка `bot_token` с оборванной
      кавычкой даёт отказ, в тексте которого нет ни одной подстроки значения,
      но есть номер строки и имя ключа
- [x] 1а.5 Проверить тестом, что несовпадение типов (строка вместо числа)
      по-прежнему называет ключ и типы

## 2. Сборка входа

- [x] 2.1 Сменить подпись `buildTelegram` на приём `config.TelegramConfig`
      целиком и поправить вызов в `main.go`
- [x] 2.2 Вынести решение в `telegramFromConfig(cfg, newBot, logger)`, где
      `newBot` — параметр-функция сборки клиента; `buildTelegram` подставляет
      `telegram.NewBot`. Ветка выключенного входа стоит **до** вызова `newBot`:
      выставляет признак поднятости в ноль, пишет одну строку уровня `INFO` и
      возвращает заглушку отправителя
- [x] 2.3 Свести в `telegramFromBot` ветку `telegram.ErrEmptyToken` с веткой
      отказа Bot API: оба исхода — ошибка настройки, старт роняется
- [x] 2.4 Обновить комментарий-разрез над `telegramFromBot`: он описывает три
      исхода по прежнему разрезу

## 3. Проверки поведения

- [x] 3.1 Заменить тест `TestTelegramFromBotOnEmptyTokenGivesAbsentSender`
      проверкой нового исхода: пустой ключ при включённом входе роняет старт,
      заглушка не подставляется
- [x] 3.2 Написать тест на выключенный вход через `telegramFromConfig`: старт не
      падает, ядро получает заглушку, в журнале ровно одна запись уровня `INFO`
- [x] 3.3 Проверить главное утверждение выключенного входа **счётчиком, а не
      исходом**: `telegramFromConfig` с `Enabled = false` и заполненным
      (заведомо ненастоящим) ключом зовёт подставную сборку **ноль раз**. Судить
      по «бот не заведён, отказа нет» нельзя: эти утверждения остаются верными и
      тогда, когда ветка встала после обращения, а обращение ушло в живой
      Telegram
- [x] 3.4 Оставшиеся тесты `telegramFromBot` (отказ Bot API, недоступность,
      живой бот) прогнать без правок по существу

## 4. Настройки и документы

- [x] 4.1 Добавить `enabled` в секцию `[telegram]` образца `config.dist.toml`
      со значением `false` и комментарием: зачем поле, что значит каждое
      значение, что ключ обязателен и умолчания у него нет
- [x] 4.2 Переписать комментарий к `bot_token` в образце: он больше не отвечает
      за включение входа
- [x] 4.3 Поправить запрет «Боевым токеном бота не запускаться» в `CLAUDE.md`:
      локальный прогон идёт с `enabled = false`, а не с пустым токеном
- [x] 4.0 Записать в `docs/conventions/config.md`, раздел «Секреты», правило
      «отказ загрузки настроек не несёт содержимого файла» с причиной: текст
      отказа собирает чужая библиотека, и разбираемый кусок попадает в него
      целиком
- [x] 4.4 Поправить `docs/conventions/config.md` в **двух** разделах: «Проверка
      и остановка на старте» описывает прежний разрез по пустоте токена, а
      «Структура в коде» утверждает, что новое поле требует правки обоих мест,
      включая `defaultConfig()`. Записать там форму обязательного поля без
      умолчания, иначе следующий такой ключ получит угаданное намерение обратно
- [x] 4.5 Поправить `docs/architecture.md` в **двух** местах: строку перечня
      capability («незаданный вход Telegram не мешает подъёму» — после изменения
      ложно и ссылается на снятое имя требования) и строку про Telegram в
      таблице отказов
- [x] 4.6 Поправить `docs/review.md`: рецепт живого прогона там велит поднимать
      сервис с пустым `telegram.bot_token`, а после изменения так он не встанет
- [ ] 4.7 Завести ADR о том, что намерение объявляется признаком, а не выводится
      из ключа доступа, и проставить парный статус
      `ADR-2026-08-13-telegram-outage-does-not-block-startup`: он утверждает, что
      старт роняет ровно один исход сборки клиента, а после изменения их два.
      Заводить через скилл `av-dev:doc-sync`, на шаге синка документации

## 5. Гейт и живой прогон

- [ ] 5.1 `task gate` зелёный — **не выполнено, и причина не в этой работе**:
      шаги `docs` и `tasks` красные оба по одной причине — проект приведён к
      раскладке av-dev версии 3, а плагин ждёт версии 4. Проверено на чистом
      `HEAD` в отдельном рабочем дереве: там те же два шага и то же
      расхождение. Чинится операцией `upgrade` скилла `av-dev:canon`, и это
      отдельная работа. Прочие шаги гейта зелёные
- [x] 5.2 Живой прогон: подъём с `enabled = false` — сервис встаёт, в журнале
      одна запись `INFO`, признак поднятости входа Telegram равен нулю
- [x] 5.3 Живой прогон: подъём с `enabled = true` и пустым `bot_token` — процесс
      выходит с ненулевым кодом, сообщение называет ключ
- [x] 5.4 Живой прогон: подъём с секцией `[telegram]` без ключа `enabled` —
      процесс выходит с ненулевым кодом, сообщение называет недостающий ключ
