- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет, файл без него негоден; bot_token стал только ключом доступа и при enabled = false не читается вовсе, а пустой при enabled = true роняет старт - выключенный вход даёт подъём одним входом без единого обращения к Telegram и записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение - отказ разбора файла настроек больше не пересказывает toml — её ParseError несёт в тексте разбираемое значение, и оборванная строка секретного ключа уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
15 KiB
15 KiB
Критерии приёмки
Постановка пришла текстом и критериев не назвала. Ниже — предложенные; данными они становятся после ответа на чекпоинте.
- В секции
[telegram]файла настроек есть ключenabled, и он один решает, поднимается ли вход. Ключ доступа второго значения не несёт. - Файл настроек с
enabled = falseдаёт подъём одним входом, к Telegram не уходит ни одного обращения, а в журнале ровно одна запись уровняINFO. - Файл настроек с
enabled = trueи пустымbot_tokenроняет старт; сообщение называет имя ключа и не содержит его значения. - Файл настроек без ключа
enabledнегоден: загрузка кончается отказом, и сообщение называет недостающий ключ. Умолчания у признака нет. - Прежние правила при включённом входе сохранены: отказ Bot API роняет старт,
недоступность Telegram даёт подъём с записью уровня
WARN. - Признак поднятости входа Telegram выставляется во всех случаях, включая выключенный.
task gateзелёный.
Ниже — рубрика ревью дизайна, теми же критериями. Пункты, целиком совпавшие с перечнем выше, не повторяются.
- Таблица режимов полна. Для каждой комбинации «признак задан или нет × признак истинен или ложен × ключ доступа пуст, непуст или подсказка» назван ровно один исход из трёх: подъём с ботом, подъём без бота, отказ старта.
- Опечатка не выключает вход молча.
enable,Enabled, ключ в чужой секции, отсутствующая секция — каждый случай даёт отказ, а не тихий выбор режима по нулевому значению. - Проверка целиком предшествует необратимому. Приговор о настройках выносится до открытия порта, до применения шагов схемы и до создания каталогов.
- Выбранный режим наблюдаем, и наблюдаемость различает основания. Из журнала и метрик видно и «работает ли вход сейчас», и «по какому основанию он не поднят»: выбор владельца, ошибка настройки, недоступность собеседника.
- Решение о режиме принимается один раз и в одном месте. Порядок «умолчания → файл → приговор» зафиксирован; ни один потребитель не пересчитывает «поднят ли вход» из полей настроек самостоятельно.
- Виды отказа различимы по сообщению: файла нет, файл не разбирается, ключ не задан, ключ задан негодно — по каждому видно, что чинить, и код выхода ненулевой.
- Выключенный вход не оставляет хвостов. Клиент не заводится, сетевого обращения нет, остановка не ждёт несуществующего собеседника.
- Обратная совместимость файла названа в обе стороны — что делает новый код со старым файлом и старый код с новым, вместе с порядком выкладки и условиями отката.
- Отсутствие умолчания объявлено там, где записана конвенция, а не только комментарием в коде.
- Отказ разбора файла настроек не несёт содержимого файла. Поломанная строка секретного ключа даёт отказ с номером строки и именем ключа, но без единой подстроки значения. Несовпадение типов при этом по-прежнему называет ключ и типы.
1. Настройки
- 1.1 Добавить поле
Enabled boolс тегомtoml:"enabled"вconfig.TelegramConfig; умолчания вdefaultConfig()для него не заводить и объяснить это комментарием - 1.2 Поднять
MetaDataизtoml.DecodeFileвLoadConfigи отказывать в загрузке, когда ключtelegram.enabledв файле не задан; сообщение называет ключ.MetaDataнаружу изLoadConfigне отдавать - 1.3 Написать
TelegramConfig.Validate()по образцуAuthConfig.Validate(): приEnabledи пустомBotTokenвернуть отказ с именем ключаbot_tokenи без его значения - 1.4 Позвать
cfg.Telegram.Validate()вmain.goрядом с проверкой секции[auth]; отказ роняет процесс черезlogger.Errorиos.Exit(1) - 1.5 Проверить
TelegramConfig.Validate()тестами: включён и ключ есть — ошибки нет; включён и ключ пуст — ошибка называетbot_tokenи не несёт значения; выключен и ключ пуст — ошибки нет - 1.6 Проверить
LoadConfigтестом на временном файле: секция[telegram]без ключаenabledдаёт отказ с именем ключа; с ключом — загрузка проходит и значение доезжает обоими значениями
1а. Отказ разбора не несёт содержимого файла
- 1а.1 В
LoadConfigперестать заворачивать отказtoml.DecodeFileчерез%w: разобрать его по семействам и собрать сообщение самому - 1а.2
toml.ParseError(черезerrors.As) — взять путь, строку, столбец и последний ключ; полеMessageв сообщение не брать - 1а.3 Прочие отказы декодера — взять текст как есть: он собран из имён ключей и типов. Причину разреза записать комментарием, иначе следующая правка сведёт две ветки в одну
- 1а.4 Проверить тестом на временном файле: строка
bot_tokenс оборванной кавычкой даёт отказ, в тексте которого нет ни одной подстроки значения, но есть номер строки и имя ключа - 1а.5 Проверить тестом, что несовпадение типов (строка вместо числа) по-прежнему называет ключ и типы
2. Сборка входа
- 2.1 Сменить подпись
buildTelegramна приёмconfig.TelegramConfigцеликом и поправить вызов вmain.go - 2.2 Вынести решение в
telegramFromConfig(cfg, newBot, logger), гдеnewBot— параметр-функция сборки клиента;buildTelegramподставляетtelegram.NewBot. Ветка выключенного входа стоит до вызоваnewBot: выставляет признак поднятости в ноль, пишет одну строку уровняINFOи возвращает заглушку отправителя - 2.3 Свести в
telegramFromBotветкуtelegram.ErrEmptyTokenс веткой отказа Bot API: оба исхода — ошибка настройки, старт роняется - 2.4 Обновить комментарий-разрез над
telegramFromBot: он описывает три исхода по прежнему разрезу
3. Проверки поведения
- 3.1 Заменить тест
TestTelegramFromBotOnEmptyTokenGivesAbsentSenderпроверкой нового исхода: пустой ключ при включённом входе роняет старт, заглушка не подставляется - 3.2 Написать тест на выключенный вход через
telegramFromConfig: старт не падает, ядро получает заглушку, в журнале ровно одна запись уровняINFO - 3.3 Проверить главное утверждение выключенного входа счётчиком, а не
исходом:
telegramFromConfigсEnabled = falseи заполненным (заведомо ненастоящим) ключом зовёт подставную сборку ноль раз. Судить по «бот не заведён, отказа нет» нельзя: эти утверждения остаются верными и тогда, когда ветка встала после обращения, а обращение ушло в живой Telegram - 3.4 Оставшиеся тесты
telegramFromBot(отказ Bot API, недоступность, живой бот) прогнать без правок по существу
4. Настройки и документы
- 4.1 Добавить
enabledв секцию[telegram]образцаconfig.dist.tomlсо значениемfalseи комментарием: зачем поле, что значит каждое значение, что ключ обязателен и умолчания у него нет - 4.2 Переписать комментарий к
bot_tokenв образце: он больше не отвечает за включение входа - 4.3 Поправить запрет «Боевым токеном бота не запускаться» в
CLAUDE.md: локальный прогон идёт сenabled = false, а не с пустым токеном - 4.0 Записать в
docs/conventions/config.md, раздел «Секреты», правило «отказ загрузки настроек не несёт содержимого файла» с причиной: текст отказа собирает чужая библиотека, и разбираемый кусок попадает в него целиком - 4.4 Поправить
docs/conventions/config.mdв двух разделах: «Проверка и остановка на старте» описывает прежний разрез по пустоте токена, а «Структура в коде» утверждает, что новое поле требует правки обоих мест, включаяdefaultConfig(). Записать там форму обязательного поля без умолчания, иначе следующий такой ключ получит угаданное намерение обратно - 4.5 Поправить
docs/architecture.mdв двух местах: строку перечня capability («незаданный вход Telegram не мешает подъёму» — после изменения ложно и ссылается на снятое имя требования) и строку про Telegram в таблице отказов - 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, и это отдельная работа. Прочие шаги гейта зелёные - 5.2 Живой прогон: подъём с
enabled = false— сервис встаёт, в журнале одна записьINFO, признак поднятости входа Telegram равен нулю - 5.3 Живой прогон: подъём с
enabled = trueи пустымbot_token— процесс выходит с ненулевым кодом, сообщение называет ключ - 5.4 Живой прогон: подъём с секцией
[telegram]без ключаenabled— процесс выходит с ненулевым кодом, сообщение называет недостающий ключ