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

161 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Критерии приёмки
Постановка пришла текстом и критериев не назвала. Ниже — **предложенные**;
данными они становятся после ответа на чекпоинте.
- В секции `[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`
процесс выходит с ненулевым кодом, сообщение называет недостающий ключ