docs: правила о контексте и о токене записаны, две находки — в журнал
- в go-linters.md заведён раздел «Отмена и внешний собеседник», перечень механизированного пополнен девятью правилами и двумя шагами гейта, названы остатки: contextcheck не видит сигнатуру без контекста вовсе, шаг migrations судит только шаги, бывшие в базе диффа, rowserrcheck и sqlclosecheck профилактические — предмета в коде нет - в logging.md и security.md чистка отказа Telegram описана по факту: точка одна и лежит на границе клиента, закрыты все пять путей вместе с логгером самой библиотеки. Прежнее «*Расхождение:* вычистки нет... она не логируется» было неверным дважды - в журнал дефектов записаны две находки: отказ скачивания уносил токен бота (проскочил, жил с самого начала) и остановка сервиса хоронила конвертируемую запись в failed (поймано ревью до коммита) - вопрос темы operations про отмену переформулирован: спрашивать надо не «доходит ли контекст», а «что шаг делает с задачей, деньгами и ответом отправителю»; вопрос про таймаут оставлен с оговоркой, что проброс контекста на него не отвечает - в памятке: словарь кодов новых шагов, требование компилятора C у детектора гонок и оговорка, что «CGO не нужен» относится к сборке, а не к гейту
This commit is contained in:
+79
-5
@@ -116,12 +116,18 @@
|
||||
|
||||
Форма: `<тема>: <вопрос> (<провенанс>)`.
|
||||
|
||||
- `operations`: пережил ли шаг конвейера отмену контекста на середине — воркеры
|
||||
получают `ctx`, но ни один шаг его внутрь не передаёт (чтение `worker.go` и
|
||||
`transcribe.go`, 2026-08-10).
|
||||
- `operations`: как шаг отвечает на отмену посреди работы — контекст доходит до
|
||||
внешнего собеседника и это держат правила `noctx` и `contextcheck`
|
||||
([conventions/go-linters.md](conventions/go-linters.md), «Отмена и внешний
|
||||
собеседник»), а исход прерванного шага нормой по-прежнему не описан
|
||||
(`openspec/specs/pipeline`, `Purpose`). Спрашивать надо не «доходит ли», а «что
|
||||
делает с задачей, деньгами и ответом отправителю» (чтение `worker.go` и
|
||||
`transcribe.go`, 2026-08-13; прежний провенанс 2026-08-10 устарел вместе с
|
||||
дефектом «остановка хоронила запись»).
|
||||
- `operations`: появился ли таймаут у обращения к Telegram, S3 и SpeechKit — ни у
|
||||
одного из них таймаута нет (чтение `tg.go`, `s3.go`, `speechkit.go`,
|
||||
2026-08-10).
|
||||
одного из них таймаута нет, и проброс контекста на этот вопрос **не отвечает**:
|
||||
контекст здесь несёт жизнь процесса, а не дедлайн вызова (чтение `tg.go`,
|
||||
`s3.go`, `speechkit.go`, 2026-08-13).
|
||||
- `operations`: не удвоилась ли запись об одном сбое — шаг логирует ошибку и
|
||||
возвращает её воркеру, который логирует снова (чтение `transcribe.go`,
|
||||
2026-08-10).
|
||||
@@ -240,6 +246,74 @@ API и имя не откатываются обратной правкой по
|
||||
поймать их было некому. У восстановленных нет поля «Чем воспроизведён», и
|
||||
выдумывать его задним числом нельзя.
|
||||
|
||||
## 2026-08-13 — остановка сервиса хоронила конвертируемую запись [пойман ревью]
|
||||
|
||||
- **Где:** `internal/service/transcribe.go`, шаг конвертации — дефект завела та
|
||||
же правка, что проложила контекст до `ffmpeg`
|
||||
- **Симптом:** на прод не уехал, поймали до коммита. Выглядел бы так: обычная
|
||||
выкладка посреди конвертации переводит здоровую запись в терминальное
|
||||
`failed`, отправителю уходит «сбой конвертации файла», а вернуть задачу может
|
||||
только владелец правкой в панели. Окно — часы: конвертация шестичасовой записи
|
||||
идёт дольше часа по построению
|
||||
- **Причина:** контекст дошёл до внешнего процесса, а различать его отмену шаг
|
||||
не научили. Убитый по контексту `ffmpeg` отдаёт `signal: killed` — от
|
||||
настоящего отказа (`exit status N`) эта ошибка неотличима ни типом, ни
|
||||
`errors.Is`: различает только `ctx.Err()`. Шаг звал `failJob` на любой отказ
|
||||
`Convert`. Хуже: `failJob` возвращает `nil`, поэтому воркер считал прогон
|
||||
успешным, и метрика владельца — та, которой он замечает отказы, — не
|
||||
шевелилась
|
||||
- **Чем воспроизведён:** проверкой `TestShutdownDuringConversionKeepsJobRetryable`
|
||||
с подставным конвертером, ведущим себя как убитый процесс: отдаёт отказ, не
|
||||
несущий `context.Canceled`. Мутация снята — без развилки проверка краснеет
|
||||
- **Почему не поймали раньше:** правка выглядела механической, «линтер потребовал
|
||||
контекст». Цена оказалась в семантике очереди, а не в сигнатурах: отмена стала
|
||||
значить разное на соседних шагах одного конвейера. Ни один линтер такого не
|
||||
видит — это заметили три прохода ревью независимо, и все три построили путь
|
||||
- **Что меняем:** прерванный шаг приговора не выносит — задача остаётся на
|
||||
повтор, попытку не тратит (счётчик, выросший при захвате, возвращают назад) и
|
||||
отправителю о несуществующем сбое не сообщает. Воркер не считает остановку
|
||||
отказом и не пишет о ней владельцу. Задача не забирается вовсе, если нас уже
|
||||
остановили. Остаток объявлен: норма отмены в спеке `pipeline` не описана, и
|
||||
открытая задача `context-cancel-in-pipeline` этим закрыта не целиком
|
||||
|
||||
## 2026-08-13 — отказ скачивания уносил токен бота в журнал [проскочил]
|
||||
|
||||
- **Где:** `internal/controller/tg/tg.go`, скачивание записи по ссылке
|
||||
`file.Link(c.bot.Token)`
|
||||
- **Симптом:** не наблюдался, потому что журнал за этим местом никто не читал
|
||||
построчно. Первый же сбой сети на скачивании писал в журнал
|
||||
`Failed to download audio file` вместе с полным адресом запроса, а в адресе
|
||||
Telegram держит токен бота (`…/bot<TOKEN>/…`). Инвариант «секрет не покидает
|
||||
конфиг» помечен critical и необратим: утёкший токен отзывают руками
|
||||
- **Причина:** `http.Get` возвращает `*url.Error`, и тот встраивает адрес
|
||||
целиком. Отказ уходил в `fmt.Errorf("failed to download file: %w", err)`, а
|
||||
оттуда — в `logger.Error` соседней строкой
|
||||
- **Чем воспроизведён:** чтением цепочки от `http.Get` до вызова `logger.Error`
|
||||
в трёх обработчиках; на живом боте не проверялся — боевым токеном запускаться
|
||||
запрещено
|
||||
- **Почему не поймали раньше:** правило было записано прозой и ровно про этот
|
||||
случай — [conventions/logging.md](conventions/logging.md), «Ошибка
|
||||
HTTP-транспорта несёт URL». Хуже: там же стояло объявленное *Расхождение* с
|
||||
оценкой «сегодня она не логируется — то есть утечки нет», и оценка была
|
||||
неверной. Строка лога существовала всё это время, но проза о ней не знала, а
|
||||
машина прозу не проверяет
|
||||
- **Что меняем:** чистка перенесена с места употребления на **границу клиента** —
|
||||
`internal/adapter/telegram`, `NewBot`: свой `Do` разворачивает отказ в
|
||||
первопричину, а подменённый логгер библиотеки вычищает токен из строк длинного
|
||||
опроса, которые она печатает сама, мимо нашего `slog`. Транспорт бота токена
|
||||
больше не получает: клиента ему отдают готовым. Расхождение в конвенции
|
||||
закрыто, оценка в [security.md](security.md) исправлена
|
||||
- **Чем закрыт от возврата:** проверками `internal/adapter/telegram/bot_test.go`
|
||||
— четыре пути (`getFile`, `sendMessage`, конструктор, логгер библиотеки)
|
||||
судятся по тексту отказа и строке журнала. Мутация снята: со снятой чисткой
|
||||
три из них краснеют, печатая токен. Правило остаётся прозой (линтер не отличит
|
||||
ссылку с секретом от ссылки без него), но у прозы теперь есть оракул
|
||||
- **Как нашли:** первый путь — попутно, при разборе находок `noctx`: тот
|
||||
потребовал переписать `http.Get` на запрос с контекстом, и цепочку пришлось
|
||||
прочитать целиком. Остальные четыре — конвейером ревью в тот же день; правка,
|
||||
закрывшая один путь, объявила класс закрытым в двух документах, и это едва не
|
||||
осталось так
|
||||
|
||||
## 2026-08-13 — конец потока распознавания узнавался по тексту сообщения [пойман сканером]
|
||||
|
||||
- **Где:** `internal/adapter/recognizer/yandex/speechkit.go`, чтение потока
|
||||
|
||||
Reference in New Issue
Block a user