# Намерение объявляется признаком, а не выводится из ключа доступа - **Дата:** 2026-08-13 - **Источник:** openspec/changes/archive/2026-08-13-telegram-enabled-flag/design.md - **Статус:** устарело — вход Telegram убран решением [ADR-2026-08-15-telegram-intake-removed-temporarily](ADR-2026-08-15-telegram-intake-removed-temporarily.md); довод устоял и понадобится возврату входа ## Решение Вход Telegram включается отдельным признаком `telegram.enabled`, а `bot_token` означает только доступ. Признак **обязателен**: умолчания у него нет, и файл настроек без него негоден — сервис выходит с ошибкой настройки, назвав недостающий ключ. ## Почему Прежде пустой ключ доступа значил разом две вещи — «вход выключен намеренно» и «ключа нет», — и сервис поднимался без бота в обоих случаях. Цена расхождения падала на выкладку: файл настроек собирает Ansible, и потерянный при сборке ключ выглядел для сервиса как решение владельца. Умолчания у признака нет, и это **намеренный отказ от очевидного подхода** — булев ключ обычно заводят с умолчанием. Цитата из источника: > умолчание — это угаданное намерение, а признак заводится ровно затем, чтобы > намерение объявляли. Файл, где его забыли, одинаково плохо читается в обе > стороны, и любое умолчание делает одну из двух ошибок тихой. Отвергнуты оба умолчания. «Включён» — файл без признака работал бы «как-нибудь», и разница между объявленным и угаданным намерением исчезала бы ровно там, где её завели. «Выключен» — первый же подъём после выкладки выключил бы бота молча, то есть дал бы исход, против которого написано само требование. Отсутствие ключа судит **разбор**, а не значение: `toml.MetaData.IsDefined` отличает «не задан» от «задан ложным», тогда как нулевое значение `bool` у обоих одинаковое. Форма поля с указателем отвергнута: указатель пережил бы проверку и уехал к потребителям, где `nil` уже невозможен, но выглядит возможным. Тем же решением закрыт разрез текста отказа при разборе файла настроек. Цитата из источника: > Пересказывать библиотеку нельзя: она собирает текст отказа из разбираемого > куска файла, и оборванная строка секретного ключа уехала бы в журнал вместе со > значением. Норму держит инвариант «Секрет не покидает конфиг», а форму записи — конвенция настроек. Спеки загрузку настроек не нормируют, и это назначено явно: загрузка не принадлежит ни одной заведённой capability. ## Последствия - `+` потерянный при сборке файла ключ доступа роняет старт вслух, а не оставляет сервис работать в половину силы; - `+` выключенный вход перестал быть поводом для предупреждения: решение владельца сообщается записью «к сведению», а предупреждение осталось за тем, чего владелец не выбирал, — недоступностью Telegram; - `+` оборванная строка секретного ключа больше не уносит значение в журнал контейнера; - `−` **порядок выкладки стал обязательным**: шаблон настроек обязан получить признак раньше накатки образа, иначе сервис не поднимется вовсе. Правило живёт в [architecture.md](../architecture.md), раздел «Эксплуатация», и задаётся там по ключу, а не по файлу целиком; - `−` один путь молчаливой потери бота остался: признак, ошибочно собранный как «выключен», отличим от решения владельца только записью журнала. Признак поднятости входа тут не помощник — он равен нулю и при недоступности Telegram; - `−` отказ разбора файла настроек стал беднее на текст библиотеки: место и ключ названы, а что именно в строке не так — нет. Плата принята ради инварианта, помеченного необратимым.