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
+47 -4
View File
@@ -22,6 +22,15 @@
новое поведение видно в выводе). Признак дешёвый: если ожидаемого нового поля,
метрики или строки нет вовсе — вероятнее всего, отвечает не твой процесс.
**Ни один проход не сообщает свой потолок, и это надо читать как границу
покрытия.** Прогон `telegram-enabled-flag` 2026-08-13: у прохода есть потолок
находок, и устав велит объявлять строкой, сколько осталось за срезом и какого
рода. Ни один из четырёх проходов такой строки не дал, и заметил это только
триаж. Пока так, «находок больше нет» в отчёте прохода неотличимо от «больше не
поместилось». Выше прочих риск у прохода, вбирающего темы разом: у него одна
квота на три темы. Механизации нет — потолок объявляет сам проход, и заставить его нечем;
остаётся сверка триажа.
Что уже проверяет машина и о чём поэтому спрашивать не нужно — конвенция
[conventions/go-linters.md](conventions/go-linters.md). Вопросы ниже — то, чего
машина не проверяет; свойства, которые обязан проверять тест, — в «Типовых
@@ -240,11 +249,13 @@ API и имя не откатываются обратной правкой по
`adapter/metaviewer/ffmpeg` нет; решение и его цена — в
[adr/ADR-2026-08-11-stub-adapters-in-tests.md](adr/ADR-2026-08-11-stub-adapters-in-tests.md);
- **работа сервиса с настоящими внешними собеседниками.** Сам сервис поднять
теперь можно: с пустым `telegram.bot_token` он встаёт и работает одним входом
(`openspec/specs/intake`, «Недоступный или незаданный вход Telegram не мешает
подъёму»). Живой прогон — осмотр HTTP, панели, журнала и остановки — доступен
теперь можно: с `telegram.enabled = false` он встаёт и работает одним входом
(`openspec/specs/intake`, «Признак включения решает, поднимается ли вход
Telegram»). Живой прогон — осмотр HTTP, панели, журнала и остановки — доступен
теперь любой задаче. Прежняя формулировка «всё, что требует поднять сервис целиком»
снята задачей `local-run-without-telegram-token` 2026-08-13.
снята задачей `local-run-without-telegram-token` 2026-08-13; рецепт прогона
сменился с пустого ключа доступа на выключенный вход задачей
`telegram-enabled-flag` того же дня.
**Остаток**: за настоящий Telegram, SpeechKit и Object Storage живой прогон
по-прежнему не отвечает — боевым токеном запускаться запрещено, ключи Yandex в
@@ -260,6 +271,38 @@ API и имя не откатываются обратной правкой по
истории git 2026-08-10: поле «Чем воспроизведён» называет у них коммит, а не
оракул, и выдумывать оракул задним числом нельзя.
## 2026-08-13 — сторож инварианта про секрет искал подстроку, которой не бывает [пойман ревью]
- **Где:** `internal/config/config_test.go`, проверка «значение ключа доступа не
попадает в отказ» задачи `telegram-enabled-flag`. Дефект в самой проверке, кода
сервиса он не касался
- **Симптом:** проверка была зелёной и утверждала, что отказ `TelegramConfig.Validate()`
не несёт значения ключа доступа. Приёмочный критерий задачи считался закрытым ею
- **Причина:** двойная, и каждая половина достаточна. Утверждение искало
подстроку `enabled = true при`, а в сообщении стоит `при enabled = true`
порядок слов обратный, и такой подстроки не бывает ни при каком входе. Глубже:
`Validate()` отказывает **только** на пустом ключе, то есть значения, которым
можно проговориться, на этом пути не существует вовсе. Комментарий при этом
утверждал «Ключ непуст», а в теле стояло `BotToken: ""` — описан был не тот
вход, который задан
- **Чем воспроизведён:** триаж скопировал дерево во временный каталог и заменил
тело `Validate()` на утекающее — `fmt.Errorf("... bot_token=%q ...", c.BotToken)`.
Проверка осталась зелёной
- **Почему не поймали раньше:** проверка написана в той же задаче и той же рукой,
что и код; гейт зелёный, а зелёная проверка неотличима от работающей. Поймали
два прохода независимо — разбор кода и сверка требований
- **Что меняем:** проверка переписана честно и переименована: половина требования
«сообщение не несёт значения» на этом пути **вакуумна**, и это названо прямо, а
настоящий сторож той же нормы указан по имени — он живёт там, где непустой ключ
в отказ попасть действительно может, в проверках отказа разбора файла настроек.
Класс всплывает **третий раз** (2026-08-11 «проверка приёма не могла упасть»,
2026-08-12 «проверка не могла упасть: читала живую карту заголовков»), и в этот
раз он другой природы: прежние два ловились правилом линтера про источник
утверждения, а этот — про **вход**: у сторожа утечки вход обязан содержать
значение, которое может утечь, иначе сторож пуст независимо от формы
утверждения. Механизации у этого нет и, похоже, быть не может: «может ли здесь
вообще утечь» — суждение, а не форма. Остаётся проходу ревью
## 2026-08-13 — остановка сервиса хоронила конвертируемую запись [пойман ревью]
- **Где:** `internal/service/transcribe.go`, шаг конвертации — дефект завела та