config: включение Telegram разведено с ключом доступа

- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет,
  файл без него негоден; bot_token стал только ключом доступа и при
  enabled = false не читается вовсе, а пустой при enabled = true роняет старт
- выключенный вход даёт подъём одним входом без единого обращения к Telegram и
  записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение
- отказ разбора файла настроек больше не пересказывает toml — её ParseError
  несёт в тексте разбираемое значение, и оборванная строка секретного ключа
  уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
This commit is contained in:
av
2026-08-13 21:46:14 +03:00
parent 903941f587
commit cd57b68215
27 changed files with 1536 additions and 123 deletions
@@ -0,0 +1,63 @@
# Ревью дизайна — telegram-enabled-flag
Метка `medium`, назначена агентом `review-scope` (размер среднее, сложность
знакомое). Режим по графу. Состав по метке: `specs` (режим «дизайн ДО кода») и
`rubric`. Триажа на этой стадии нет — сток стадии — шаг отработки замечаний.
## Находки и что с ними сделано
| Проход | Находка | Severity | Исход |
| --- | --- | --- | --- |
| specs | У выключенного входа нет оракула: «бот не заведён, отказа нет» остаётся верным и когда ветка встала **после** обращения, а обращение ушло боевым токеном в живой Telegram | major | принята. Заведён шов `telegramFromConfig(cfg, newBot, logger)`, шаг 3.3 судит по счётчику вызовов, а не по исходу |
| specs | `ADR-2026-08-13-telegram-outage-does-not-block-startup` утверждает, что старт роняет ровно один исход сборки клиента; после изменения их два | minor | принята. Шаг 4.7: новый ADR и парный статус прежнему |
| specs | `docs/architecture.md` (перечень capability) и `docs/review.md` (рецепт живого прогона) останутся ложными: они учат поднимать сервис пустым токеном | minor | принята. Шаги 4.5 и 4.6 |
| specs | Ошибочный `enabled = false` из шаблона выкладки — оставшееся русло молчаливой потери бота — в рисках не назван | minor | принята. Строка в `Risks / Trade-offs` |
| rubric | Отказ разбора файла настроек может унести секрет в журнал: `toml.ParseError` встраивает разбираемое значение в текст | major | **снята из объёма, ушла в урожай.** Путь существует сегодня (`LoadConfig` заворачивает через `%w`, `main.go:49` печатает целиком) и этой работой не заводится; у починки своя цена — потеря подробности отказа |
| rubric | Задача, принятая из Telegram до выключения входа, завершится, а ответ не уйдёт | major | **снята: ложноположительная.** Уже нормировано `openspec/specs/pipeline/spec.md`, «Недоставленный ответ не роняет шаг», сценарий «Вход отправителя не поднят»: `WARN`, метрика, идентификатор задачи. Проход читал только `intake`; `specs` пришёл к тому же выводу независимо |
| rubric | Откат образа при `enabled = false` и непустом ключе тихо поднимает выключенного бота | minor | принята. Абзац в `Migration Plan` |
| rubric | `docs/conventions/config.md`, раздел «Структура в коде», останется утверждать, что умолчание есть у каждого поля | minor | принята. Шаг 4.4 расширен на второй раздел |
## Правки, сделанные по урожаю
Дельта-спеки не менялись ни одной правкой — значит разметка не повторялась и
метка осталась `medium`. Правки легли в `design.md` (шов сборки, два риска,
абзац отката) и в `tasks.md` (шаги 2.2, 3.2, 3.3, 4.4, 4.5, 4.6, 4.7, рубрика в
критерии приёмки).
## Сознательно не сделано
Сценарий «Вход выключен» не получил строки `**AND** признак поднятости входа
Telegram равен нулю`. Её держит соседнее требование «Поднятые входы видны
наблюдателю», чей сценарий стоит на премиссе «сервис поднялся без Telegram» и
новое состояние покрывает. Правка изменила бы дельта-спеку и потребовала бы
повторной разметки, не дав сегодня ничего.
## Границы спеки — что осталось неопределённым
- **Небулево значение признака** (`enabled = "yes"`) попадает в общий отказ
разбора и имени ключа не называет, хотя оба соседних сценария отказа этого
требуют.
- **Несколько негодных секций разом**: `[auth]` и `[telegram]` проверяются
порознь, и спека не говорит, обязан ли отказ перечислить все ключи.
- **`update_timeout` при выключенном входе** — читается или игнорируется, не
нормировано. Вреда нет, но вопрос стал видимым: сборка получает секцию целиком.
- **Проба готовности при выключенном входе** ни одним требованием не связана с
признаком. Граница существовала и до изменения.
## Границы покрытия стадии
- Кода нет по построению: направление `spec → code` недоступно, судилось только
задуманное.
- Рубрика составлена не открывая код и дизайн — иначе она подстроилась бы под
увиденное.
- Решения (`docs/adr/`) и измеренные числа (`docs/research/`) прогон ревью не
открывает: расхождение изменения с записанным решением ловит не он, а сверка
документации. Здесь оно всё же всплыло — проход `specs` наткнулся на ADR через
ссылку из `design.md`, а не обходом каталога.
- Ничего не запускалось: `openspec validate --strict` — единственная выполненная
команда.
- `review-architecture` на предложении не запускался: он живёт с метки `large`.
Вопрос «не появился ли второй способ делать то же самое» на этом изменении не
задавал никто.
- Шаблон настроек в `pet-project-server` лежит в чужом репозитории и во вход не
входил ни одному проходу.