config: включение Telegram разведено с ключом доступа
- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет, файл без него негоден; bot_token стал только ключом доступа и при enabled = false не читается вовсе, а пустой при enabled = true роняет старт - выключенный вход даёт подъём одним входом без единого обращения к Telegram и записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение - отказ разбора файла настроек больше не пересказывает toml — её ParseError несёт в тексте разбираемое значение, и оборванная строка секретного ключа уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
## Критерии приёмки
|
||||
|
||||
Постановка пришла текстом и критериев не назвала. Ниже — **предложенные**;
|
||||
данными они становятся после ответа на чекпоинте.
|
||||
|
||||
- В секции `[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` —
|
||||
процесс выходит с ненулевым кодом, сообщение называет недостающий ключ
|
||||
Reference in New Issue
Block a user