Files
transcriber/openspec/changes/archive/2026-08-13-telegram-enabled-flag/tasks.md
T
av cd57b68215 config: включение Telegram разведено с ключом доступа
- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет,
  файл без него негоден; bot_token стал только ключом доступа и при
  enabled = false не читается вовсе, а пустой при enabled = true роняет старт
- выключенный вход даёт подъём одним входом без единого обращения к Telegram и
  записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение
- отказ разбора файла настроек больше не пересказывает toml — её ParseError
  несёт в тексте разбираемое значение, и оборванная строка секретного ключа
  уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
2026-08-13 21:46:14 +03:00

15 KiB
Raw Blame History

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

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

  • В секции [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 — процесс выходит с ненулевым кодом, сообщение называет недостающий ключ