- расширение приводится к перечню известных форматов прежде метки метрики: страница метрик открыта, и хвост имени уезжал на неё дословно - проверки приёма перехватывают все три потока журнала и читают реестр метрик, каждая падает при снятии того, что сторожит
803 lines
66 KiB
Markdown
803 lines
66 KiB
Markdown
# Триаж ревью кода: `no-user-filename-in-log`
|
||
|
||
Документ ведётся по кругам. Наверху — последний круг, ниже приложением — отчёт
|
||
первого круга целиком, как он был написан. История не переписывается: исход
|
||
каждой находки первого круга проставлен отдельным разделом, а не правкой её
|
||
текста.
|
||
|
||
---
|
||
|
||
# Круг 2 (после контрольной точки о метке метрики)
|
||
|
||
## Сводка
|
||
|
||
- **Change:** `no-user-filename-in-log`. База диффа `origin/master`, судится
|
||
рабочее дерево (`git diff`) плюс три файла не под git:
|
||
`internal/metrics/format_label.go`, `internal/metrics/format_label_test.go`,
|
||
`internal/service/format_label_test.go`. Итого 5 изменённых файлов
|
||
(+258/−17) и 3 новых.
|
||
- **Размер:** среднее. **Сложность:** знакомое. **Метка: medium** — максимум по
|
||
осям, не менялась между кругами. Второй круг добавил файл в
|
||
`internal/metrics`, но проектный триггер «новая метрика в `internal/metrics`»
|
||
(`docs/review.md`, «Триггеры метки») опускает до `small` именно новую
|
||
**метрику**; здесь новой метрики нет, добавлено приведение значения метки.
|
||
Метка остаётся `medium`.
|
||
- **Режим прогона:** по графу. Второй круг: `specs`, `code`, `basics`, `triage`.
|
||
- **Гейт:** красный **только объявленным долгом**. Проверено мной самим:
|
||
`go build ./...`, `go vet ./...`, `gofmt -l .`, `go test ./...` зелёные;
|
||
`golangci-lint run` даёт ровно 4 знакомых замечания (`speechkit.go:55`,
|
||
`main.go:124`, `worker.go:51`, `transcribe.go:395`);
|
||
`openspec validate no-user-filename-in-log --strict` — `is valid`.
|
||
`go test -race -count=5 ./internal/controller/http/` — `ok, 1.663s`.
|
||
Новых красных шагов нет.
|
||
- **Находок на входе второго круга:** 15 (specs 6, basics 3, code 6) + 1
|
||
кандидат в правила. За два круга — 32.
|
||
**После дедупликации по причине и отсева:** 3 в первых двух секциях
|
||
(1 блокирующая, 2 к исправлению), 3 гипотезы, 5 кандидатов в правила.
|
||
|
||
### Находка о прогоне: `autotests` на втором круге не запускался
|
||
|
||
Второй круг прогнал `specs`, `code` и `basics`. Проход `autotests` — **нет**, и
|
||
это его тема: круг добавил 130 строк в `internal/controller/http/transcribe_test.go`
|
||
и два новых тестовых файла целиком. Дом темы (`CLAUDE.md`, «Гейт» и «Команды»)
|
||
второй круг не открывал никто.
|
||
|
||
Это не абстрактный пробел: **блокирующая находка №1 ниже — ровно тот класс,
|
||
который ищет `autotests`**, и нашёл её я мутацией, а не проход. Судить, сколько
|
||
ещё такого осталось в добавленных проверках, нечем: я не читаю код в поисках
|
||
дефектов, я проверяю чужие выводы, а выводов по теме `autotests` за второй круг
|
||
не поступало.
|
||
|
||
### Сигнал о заниженной метке
|
||
|
||
- **Круг 1:** `review-code` пришёл и метку заниженной **не** считает. От
|
||
`review-basics` сигнала не было — ни «верна», ни «занижена».
|
||
- **Круг 2:** сигнала не пришло **ни от одного** прохода — ни от `review-code`,
|
||
ни от `review-basics`. Отличить «возражений нет» от «не сказано» по молчанию
|
||
нельзя, и я этого не делаю.
|
||
|
||
### План разметки против исхода — оба круга
|
||
|
||
План не менялся. Своих тем проекта сверх ядра нет, тем без дома нет.
|
||
|
||
| тема | дом | глубина | закрывает | круг 1 | круг 2 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| requirements | `openspec/specs/intake/spec.md` + дельта change | разбор | specs | **закрыта**, 4 находки | **закрыта**, 6 находок |
|
||
| autotests | `CLAUDE.md`, «Гейт» и «Команды» | — | autotests | **закрыта**, 0 находок + 1 promote | **отчёта не пришло — проход не запускался** |
|
||
| conventions | `docs/conventions/logging.md` + весь каталог | разбор | code | **закрыта**, 6 находок | **закрыта**, 6 находок |
|
||
| architecture | `docs/architecture.md`, «Единые точки проекта» + `docs/passport.md` | разбор | basics | **закрыта**, ответ по вопросу темы + 1 находка | **закрыта**, 1 находка (дом правила) |
|
||
| security | `docs/security.md`, «Куда уходит содержимое записи», «Из чего строятся пути и ключи» | разбор | basics | **закрыта**, 1 находка (главная) | **закрыта**, подтверждение: канал наружу перебран поимённо |
|
||
| operations | `docs/architecture.md`, «Эксплуатация» + `docs/database.md` | разбор | basics | **закрыта**, ответы по вопросам темы | **закрыта**, 2 находки (умолчание, видимость настоящего формата) |
|
||
|
||
---
|
||
|
||
## Блокирует мердж
|
||
|
||
### 1. Второй употребитель приведения метки оракулом не закрыт: снятие `FormatLabel` на шаге конвертации не роняет ничего
|
||
|
||
- **Где:** `internal/service/transcribe.go:212` —
|
||
`WithLabelValues(metrics.FormatLabel(srcExt), "ogg", strconv.FormatBool(err != nil))`.
|
||
Критерий `tasks.md` 2а.2 («Обе метки, несущие расширение, идут через
|
||
приведение») отмечен выполненным.
|
||
- **Severity:** major. Не критично тем, что код **сегодня верен**: приведение на
|
||
месте, хвост наружу не идёт. Критично то, что удержано оно ничем, а чек-лист
|
||
утверждает обратное.
|
||
- **Оракул (мутация на копии дерева, прогнана на этом прогоне):**
|
||
|
||
```
|
||
# копия дерева, sed по строке 212: FormatLabel(srcExt) -> srcExt
|
||
go test ./internal/...
|
||
MUT[M2 приведение снято у метки конвертации]: ЗЕЛЁНАЯ (мутация не поймана)
|
||
```
|
||
|
||
Для сравнения — тот же приём на первом употребителе краснеет:
|
||
|
||
```
|
||
MUT[M1 приведение снято у метки размера приёма]: КРАСНЕЕТ
|
||
--- FAIL: TestCreateTranscribeJob_MetricLabelCarriesNoSenderName
|
||
```
|
||
|
||
- **Что именно уходит этой меткой:** `srcExt` строится из `srcFile.FileName`
|
||
(`transcribe.go:195`), а это имя файла в хранилище вида
|
||
`<uuid>.<хвост имени отправителя>`. То есть в метку `source_format` метрики
|
||
`transcriber_conversion_duration_seconds` попадает тот же приватный хвост, что
|
||
и в метку приёма, и уезжает он на тот же анонимный `GET /metrics`
|
||
(`main.go:216`). Канал по свойствам идентичен закрытому.
|
||
- **Почему это блокирует, а не «стоит исправить»:** `design.md` этого change сам
|
||
объявил такую проверку недостаточной — дословно: «Проверка функции в отрыве от
|
||
точек употребления зелёная и при снятом приведении: правка места вызова
|
||
вернула бы хвост наружу молча». Решение принято, реализовано для одной точки
|
||
из двух, а чек-лист отмечает обе. Это записанное свойство проекта —
|
||
`docs/review.md`, «Типовые узлы / Любой узел»: «проверка **способна упасть**…
|
||
Признак ищется мутацией», и журнальная запись 2026-08-11 об этом же классе.
|
||
Ранжирую первым по правилу молчания: следующий, кто уберёт `FormatLabel` со
|
||
строки 212 как лишний вызов, сузит оракул критического инварианта, и никто не
|
||
узнает.
|
||
- **Чего эта находка НЕ утверждает:** «`/metrics` открыт без аутентификации» —
|
||
типовое ложноположительное проекта (`docs/review.md`, «Типовые
|
||
ложноположительные», пункт про HTTP API). Новое здесь не открытость, а
|
||
неудержанность приведения на второй точке.
|
||
- **Действие: развилка.**
|
||
|
||
> Приведение расширения к перечню стоит в двух местах, а оракул — только на
|
||
> одном. Мутация на втором (`transcribe.go:212`, метка `source_format` шага
|
||
> конвертации) зелёная: снятие приведения не роняет ни одной проверки.
|
||
> Своего тестового окружения у `internal/service` нет вовсе — есть только
|
||
> `format_label_test.go` на 19 строк. Что делаем до мерджа?
|
||
>
|
||
> **(а)** Мержим как есть: пробел записываем строкой в `design.md` разделом
|
||
> Risks и вешаем на существующую задачу `tasks/items/pipeline-step-tests.md`
|
||
> — она заведена ровно про непокрытые `FindAndRunConversionJob` и соседей.
|
||
> Чек-лист `tasks.md` 2а.2 при этом надо переформулировать: «приведение стоит
|
||
> на обеих точках, оракул — на одной».
|
||
> **(б)** Строим окружение проверки для `FindAndRunConversionJob`: репозитории
|
||
> SQLite, каталог хранения, подставной конвертер. Цена сопоставима с самой
|
||
> задачей и вылезает за её scope.
|
||
> **(в)** Убираем возможность обойти приведение по построению: прячем
|
||
> `InputFileSizeHistogram` и `ConversionDurationHistogram` за функциями
|
||
> `internal/metrics`, которые сами зовут `FormatLabel`. Тогда сырое расширение
|
||
> передать меткой нечем, и обе точки закрывает уже существующая проверка
|
||
> реестра. Меняется экспортируемая поверхность пакета `internal/metrics` —
|
||
> пакет внутренний, публичного контракта это не трогает, но это правка сверх
|
||
> заказанного объёма.
|
||
|
||
---
|
||
|
||
## Стоит исправить сейчас
|
||
|
||
### 2. Комментарий оракула ссылается на механизм, которого в коде нет
|
||
|
||
- **Где:** `internal/controller/http/transcribe_test.go:492` — «снятие
|
||
`log.SetOutput` из окружения оставило бы проверку зелёной». Тот же текст в
|
||
`tasks.md`, пункт 2.1: «в него же перенаправляется вывод стандартного `log`».
|
||
- **Оракул:** `grep -n "log.SetOutput" internal/controller/http/transcribe_test.go`
|
||
отдаёт единственное совпадение — саму эту строку комментария. В окружении
|
||
стоит `slog.SetDefault(logger)` (`:167`), как в `main.go:50`. Механизм заменён
|
||
в этом же круге по находке прохода `code` — комментарий и чек-лист за правкой
|
||
не поехали.
|
||
- **Почему сейчас:** причина ровно та же, что у находки №3 первого круга
|
||
(`src_ext`, метки не существует), и лечится так же — одним словом. Проза,
|
||
называющая несуществующий механизм, сбивает следующего: он не найдёт
|
||
`log.SetOutput`, решит, что утверждение мёртвое, и снимет его. Утверждение
|
||
живое — мутация подтверждает:
|
||
|
||
```
|
||
MUT[M4 slog.SetDefault снят]: КРАСНЕЕТ
|
||
--- FAIL: TestCreateTranscribeJob_SenderFileNameNotLoggedOnFailure
|
||
```
|
||
|
||
- **Действие: инлайн.** В обоих местах заменить `log.SetOutput` на
|
||
`slog.SetDefault` и уточнить формулировку: вывод стандартного `log` уходит в
|
||
буфер не перенаправлением, а тем, что `slog.SetDefault` сажает его в `msg`
|
||
записи `slog`.
|
||
|
||
### 3. Значение `other` нормировано спекой дословно, но ни одна проверка к литералу не привязана
|
||
|
||
- **Где:** `internal/metrics/format_label.go:6` — `const OtherFormatLabel = "other"`;
|
||
дельта-спека, требование «Метка метрики несёт только известное расширение»:
|
||
«всякое другое MUST заменяться единым значением `other`».
|
||
- **Оракул (мутация, прогнана):**
|
||
|
||
```
|
||
# const OtherFormatLabel = "other" -> const OtherFormatLabel = ""
|
||
MUT[M11 общее значение пустое]: ЗЕЛЁНАЯ (мутация не поймана)
|
||
```
|
||
|
||
Обе проверки — и `TestFormatLabel`, и
|
||
`TestCreateTranscribeJob_MetricLabelCarriesNoSenderName` — сверяются с самой
|
||
константой, то есть утверждают тавтологию. Значение метки — величина, которую
|
||
видит человек в панели снаружи; спека называет её дословно, а код может
|
||
сменить её молча, включая на пустую строку, при которой метка из выборок
|
||
Prometheus фактически исчезает.
|
||
- **Почему сейчас, а не в гипотезы:** это не догадка, а прогнанная мутация, и
|
||
закрывается одной строкой в `internal/metrics/format_label_test.go`:
|
||
`if OtherFormatLabel != "other" { t.Fatal(...) }` — либо заменой `want:
|
||
OtherFormatLabel` на `want: "other"` в трёх случаях таблицы.
|
||
- **Действие: инлайн.**
|
||
|
||
---
|
||
|
||
## Гипотезы без доказательства
|
||
|
||
- **Запрет `t.Parallel()` держится комментарием** (перенесено с первого круга и
|
||
остаётся верным; механизм только сменился). Теперь процессная подмена — это
|
||
`slog.SetDefault`, и ничто, кроме текста рядом, не мешает следующей проверке в
|
||
этом файле стать параллельной; тогда перехват уедет к соседу. Оракула нет:
|
||
воспроизвести можно только внесением `t.Parallel()`, то есть той самой будущей
|
||
правкой. `Confidence: medium`, severity не выше `minor`.
|
||
- **Позитивное утверждение `require.Contains(journal, msgTransportErr)`
|
||
привязано к расхождению, которое конвенция объявляет подлежащим устранению**
|
||
(`log.Printf` в HTTP-транспорте мимо `slog`). Когда расхождение починят,
|
||
проверка отказа покраснеет по причине, не связанной с приватностью. Вынос
|
||
текста в константу с комментарием про долг (сделан вторым кругом) снижает цену
|
||
правки, но событие в будущем оракулом не закрывает. `minor`.
|
||
- **Приём из Telegram своего оракула не получил ни на одном круге.** Правка
|
||
достаётся ему общим шагом `createTranscribeJob`, и обойти её со стороны
|
||
Telegram нечем — но это рассуждение по коду, а не прогон. Объявлено риском в
|
||
`design.md`. `minor`.
|
||
|
||
---
|
||
|
||
## Promote candidates
|
||
|
||
- **Правило линтера на ключи `file_name`/`filename` в вызовах логгера**
|
||
(`sloglint` либо `forbidigo`). Пришло от `autotests` на первом круге;
|
||
`design.md` объявил это non-goal задачи.
|
||
- **Свойство в `docs/review.md`, «Типовые узлы / Любой узел»:** оракул ставится
|
||
**на каждой точке употребления**, а не на самой функции и не на одной из
|
||
точек. Обобщение трёх находок подряд — неудержанного middleware (круг 1, №2),
|
||
неудержанного `log`-потока (круг 1, починено) и неудержанной точки конвертации
|
||
(круг 2, №1). Три повторения одного класса за один change — заявка на
|
||
записанное правило, а не на разовую починку.
|
||
- **Конвенции о метриках в проекте нет вовсе.** `docs/conventions/` — пять
|
||
файлов: `config`, `database`, `errors`, `logging`, `web-ui`. Что можно класть в
|
||
метку, кто отвечает за кардинальность, как объявлять смену формы значения —
|
||
дома нет. Второй круг завёл строку в `docs/architecture.md`, «Единые точки
|
||
проекта», но это указатель на реализацию, а не правило. Пришло от `code`.
|
||
- **Нормализация расширения, взятого из имени отправителя, в журнале и в имени
|
||
файла на диске** — отдельной задачей, с явным решением человека про раскладку
|
||
`data/files` (`CLAUDE.md` называет её необратимой). Остаток записан прозой в
|
||
`docs/security.md`; задачи на него нет.
|
||
- **Смена формы значения метки (`.mp3` → `mp3`) записана в `docs/security.md`.**
|
||
Дом спорный: это факт эксплуатации, а не модели угроз, и человек, у которого
|
||
опустела панель, полезет в `docs/architecture.md`, «Эксплуатация». Не находка
|
||
— правило о том, где объявляются несовместимые смены формы метрик, входит в
|
||
предыдущий пункт.
|
||
|
||
---
|
||
|
||
## Что из починенного починено верно и не ослаблено
|
||
|
||
Проверено мутациями на копии дерева (`git ls-files` + три файла не под git), не
|
||
со слов. Базовый прогон копии зелёный, откат подтверждён.
|
||
|
||
| мутация | исход | что этим удержано |
|
||
| --- | --- | --- |
|
||
| приведение снято у метки размера приёма | **КРАСНЕЕТ** (`…MetricLabelCarriesNoSenderName`) | круг 2, specs #1 — для точки приёма |
|
||
| приведение снято у метки конвертации | **ЗЕЛЁНАЯ** | **не удержано — находка №1** |
|
||
| имя отправителя вернулось под ключом `src` | **КРАСНЕЕТ** (обе проверки запрета) | круг 1: поиск идёт по значению, не по ключу |
|
||
| `slog.SetDefault` снят из окружения | **КРАСНЕЕТ** (`…NotLoggedOnFailure`) | круг 2, code #4 — боевая цепочка журнала |
|
||
| middleware не подключён к тестовому роутеру | **КРАСНЕЕТ** (`…SenderFileNameNotLogged`) | круг 1, находка №2 |
|
||
| строка приёма понижена до `DEBUG` | **КРАСНЕЕТ** (`…JournalTracesRecord`) | круг 1: адресат записи |
|
||
| умолчание сервиса выведено из перечня | **КРАСНЕЕТ** (`TestDefaultAudioExtIsKnownToMetrics` + `…DifferentFileExtensions`) | круг 2, basics #1 — дубль умолчания |
|
||
| вторая строка приёма в журнале | **КРАСНЕЕТ** (`…JournalTracesRecord`) | круг 1, находка №4 — «ровно одна запись» |
|
||
| приведение регистра снято | **КРАСНЕЕТ** (`TestFormatLabel`) | круг 2, specs #5 |
|
||
| `oga`, `opus`, `mp4` выведены из перечня | **КРАСНЕЕТ** (`TestFormatLabel`) | круг 2, code #1 — голосовые Telegram |
|
||
| `OtherFormatLabel` стал пустым | **ЗЕЛЁНАЯ** | **не удержано — находка №3** |
|
||
|
||
Дополнительно проверено поимённо, а не со слов:
|
||
|
||
- **Круг 1, находка №3 закрыта.** `docs/security.md` называет метки
|
||
`file_extension` у `transcriber_input_file_size_bytes` и `source_format` у
|
||
`transcriber_conversion_duration_seconds`. Сверено с
|
||
`internal/metrics/metrics.go:24` и `:44` — совпадает.
|
||
`grep -rn "src_ext"` по коду и документам совпадений не даёт (остались только
|
||
упоминания в архиве этого же отчёта).
|
||
- **Круг 2, specs #4 закрыта.** `design.md` держит решение о метке в Decisions,
|
||
с записью развилки и двух отвергнутых вариантов (оставить как есть; приводить
|
||
везде, включая диск). `proposal.md` согласован.
|
||
- **Круг 2, specs #3 и basics #2 закрыты.** Перечень назван поимённо в
|
||
требовании спеки (14 форматов + `audio`), дом правила — строка в
|
||
`docs/architecture.md`, «Единые точки проекта».
|
||
- **Ослаблений не найдено.** Ни одна проверка первого круга не стала слабее,
|
||
`go test ./...` зелёный, `go test -race -count=5` зелёный, новых замечаний
|
||
линтера нет, `openspec validate --strict` — valid.
|
||
|
||
---
|
||
|
||
## Что осталось непочиненным и почему
|
||
|
||
Ничего не выброшено молча. Полный список:
|
||
|
||
1. **Точка конвертации не удержана оракулом** — находка №1, развилка, решение за
|
||
человеком. Единственное, что блокирует мердж.
|
||
2. **Комментарий про `log.SetOutput`** — находка №2, инлайн, одно слово в двух
|
||
местах.
|
||
3. **Литерал `other` не привязан к спеке** — находка №3, инлайн, одна строка.
|
||
4. **Остаток «хвост расширения остаётся в журнале» задачей не заведён.** Записан
|
||
прозой в `docs/security.md` и в Risks `design.md`. Не починено **намеренно**:
|
||
заведение задач принадлежит скиллу задач, а не конвейеру ревью. Идёт урожаем
|
||
(Promote candidates, пункт 4).
|
||
5. **Конвенции о метриках нет** — урожай, Promote candidates, пункт 3. Не
|
||
находка об этом коде.
|
||
6. **Расширение в имени файла на диске не нормализуется** — решение принято
|
||
человеком на контрольной точке и записано отвергнутым вариантом в
|
||
`design.md`. Раскладка `data/files` объявлена необратимой в `CLAUDE.md`.
|
||
Закрыто как решение, не как пробел.
|
||
7. **Сценарий «Известное расширение идёт как есть» назван неточно** — тело
|
||
сценария (`sample.MP3` → `mp3`) описывает приведение регистра, заголовок
|
||
говорит «как есть». Выброшено по отсеву вкусовщины: поведения не меняет, на
|
||
стоимость следующей правки не влияет, записанной конвенции не нарушает —
|
||
текст требования выше разночтение снимает дословно.
|
||
|
||
Потолок первых двух секций **не срабатывал**: 1 из 3 и 2 из 4. За срезом не
|
||
осталось ничего.
|
||
|
||
---
|
||
|
||
## Границы покрытия
|
||
|
||
### План
|
||
|
||
Шесть тем, у каждой есть дом и глубина; все шесть перечислены в таблице выше с
|
||
исходом по каждому кругу. Тем без дома нет. Своих тем проекта сверх ядра нет.
|
||
**Тема без отчёта одна: `autotests` на втором круге** — проход не запускался,
|
||
дом темы (`CLAUDE.md`, «Гейт» и «Команды») второй круг не открывал никто, а
|
||
добавленный кругом код — это на 100% тестовый код.
|
||
|
||
### Проходы
|
||
|
||
- **Круг 1**, метка `medium`, режим «по графу»: `review-autotests`,
|
||
`review-specs`, `review-code`, `review-basics` (темы `security`, `operations`,
|
||
`architecture`, глубина разбор), `review-triage`.
|
||
- **Круг 2**, та же метка и режим: `review-specs`, `review-code`,
|
||
`review-basics`, `review-triage`.
|
||
- **Не запускались:** `review-autotests` на втором круге — причина мне не
|
||
сообщена (в задании сказано «`specs`, `code`, `basics` прогнаны заново», без
|
||
обоснования пропуска). Проходы ревью дизайна (`specs`, `rubric` на предложении)
|
||
отработали до кода и в этот прогон не входят.
|
||
|
||
### Чего в конвейере нет вовсе
|
||
|
||
Четыре строки, которые не принесёт ни один проход:
|
||
|
||
1. **Решения проекта не сверялись.** `docs/adr/` — процессный документ, прогон
|
||
его не открывает. Расхождение изменения с записанным решением ловит сверка
|
||
документации (`av-dev-docs:healthcheck`), а не ревью. В каталоге лежат четыре
|
||
ADR, включая `ADR-2026-08-11-stub-adapters-in-tests.md`, — ни один из них
|
||
этим прогоном не читался как источник требований.
|
||
2. **Записанные наблюдения проекта не использовались.** `docs/research/` — тоже
|
||
процессный. Все числа в этом отчёте сняты на этом прогоне, команды приложены.
|
||
3. **Поимённая сверка с руководством по стилю Go не задавалась ни одним
|
||
проходом.** Различение «идиоматично против распространено» — например, `map[string]struct{}`
|
||
против `slices.Contains` в `format_label.go`, или уместность тавтологичного
|
||
сравнения с константой — не спрашивал никто.
|
||
4. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера
|
||
нет.** Проход независимой реализации снят по стоимости, а не по замеру; «не
|
||
знаю, чего не знаю» никто не достаёт.
|
||
|
||
### Чего запущенные проходы не могли проверить в принципе
|
||
|
||
- Ни один проход не гонял сервис на настоящих данных: `testdata` в проекте нет
|
||
по запрету `CLAUDE.md`, реальные ключи Yandex Cloud под запретом, боевую БД и
|
||
`data/files` трогать нельзя. Все оракулы — синтетический вход и мутации.
|
||
Для находки про внешний формат это существенно: перечень из 14 расширений
|
||
собран рассуждением о том, что выдаёт Telegram и что берёт `ffmpeg`, и **ни на
|
||
одном настоящем файле не проверен**.
|
||
- `specs` судит код против заказанного поведения и не ищет дефектов вне него;
|
||
`code` судит технику и конвенции и не судит требования; `basics` идёт по трём
|
||
темам и только по их записанным домам; `autotests` судит прогон и мутации, а
|
||
не смысл проверок, — и второй круг не судил вовсе.
|
||
- Приём из Telegram своей проверки не получил ни на одном круге (объявлено
|
||
риском в `design.md`).
|
||
- Шаг конвертации (`FindAndRunConversionJob`) не покрыт ни одним тестом вообще —
|
||
это записано отдельной задачей `tasks/items/pipeline-step-tests.md`, а не
|
||
находка этого прогона.
|
||
- Триаж не читает код в поисках дефектов: пропуск любого прохода — мой пропуск
|
||
тоже. Пропуск `autotests` на втором круге назван поимённо выше.
|
||
|
||
### Что осталось целиком на человеке
|
||
|
||
Из `docs/review.md`, «Недоступно проверке», двумя отдельными списками — они не
|
||
сливаются.
|
||
|
||
**Не проверит ни один проход:**
|
||
|
||
- `operations`: поведение внешних сервисов под нагрузкой и на границах —
|
||
SpeechKit и Object Storage поднять в тесте нечем;
|
||
- `operations`: реальный профиль нагрузки. Проект живёт на единицах записей в
|
||
день, и утверждения о росте (в том числе о кардинальности метки) остаются
|
||
условиями, а не замерами;
|
||
- `security`: стойкость `ffmpeg` к вредоносному входу — разбор чужого формата
|
||
отдан внешней программе, и она вне нашей границы.
|
||
|
||
**Перестали проверять сознательно:**
|
||
|
||
- `autotests`: разбор вывода настоящего `ffprobe`. Проверки приёма получают
|
||
длительность от подставного источника; своего теста у
|
||
`adapter/metaviewer/ffmpeg` нет. Решение и его цена —
|
||
`docs/adr/ADR-2026-08-11-stub-adapters-in-tests.md`.
|
||
|
||
**Плюс общее, вне зависимости от проекта:** история инцидентов, поведение под
|
||
реальным потоком, поведение внешних систем в их версиях, завязка потребителей на
|
||
текущее поведение и вопрос «а нужна ли эта функциональность вообще». Последнее
|
||
здесь не пустое: панели и алерты, отобранные по `file_extension=".mp3"`,
|
||
перестанут пополняться после выкладки, и знает об этом только человек.
|
||
|
||
### Каких документов проекта не хватило
|
||
|
||
Строка на каждый, с причиной. Слить нельзя — чинится разным.
|
||
|
||
- **`CLAUDE.md`, «Ориентир по размеру порции: не замерялся».** Разметка «инлайн
|
||
против развилки» опирается на right-size, а мерки right-size в проекте нет.
|
||
Пометки «инлайн» в находках 2 и 3 поставлены по объёму правки (одно слово,
|
||
одна строка), и это моё предположение, а не сверка с записанным ориентиром.
|
||
- **Конвенции о метриках в `docs/conventions/` нет** — каталог есть, файла нет.
|
||
Правила «что можно класть в метку», «кто отвечает за кардинальность», «как
|
||
объявляется несовместимая смена формы значения» дома не имеют. Отсюда развилка
|
||
вместо однозначного вердикта в находке №1 и вкусовой характер вопроса о доме
|
||
записи про `.mp3` → `mp3`.
|
||
- **`docs/review.md`, «Типовые ложноположительные» — раздел есть и не пуст**,
|
||
четыре пункта; применён пункт про открытый HTTP API (дважды: к находке №1
|
||
этого круга и к находке №1 первого). Деградации по нему нет.
|
||
- Прочие нужные документы на месте и использованы: инварианты `CLAUDE.md`,
|
||
`docs/review.md` целиком, `docs/security.md`, `docs/conventions/logging.md`,
|
||
`docs/architecture.md`.
|
||
|
||
### Сработавшие потолки
|
||
|
||
- **Ни один проход ни на одном круге не сообщил свой потолок** — ни сколько
|
||
находок показал из скольких, ни что осталось за срезом. Это находка о прогоне,
|
||
повторившаяся во второй раз: судить, полон ли вход триажа, нечем. «15 находок
|
||
на входе второго круга» может означать «15 из 15», а может «15 из скольких-то».
|
||
- **Потолок триажа не срабатывал:** 1 из 3 в «Блокирует мердж», 2 из 4 в «Стоит
|
||
исправить сейчас». Ничего не выброшено из-за потолка. Выброшенное выброшено по
|
||
дедупликации (basics #1 и code #3 — одна причина, дубль умолчания; specs #1
|
||
второго круга и моя находка №1 — одна причина, приведение без оракула на точке
|
||
употребления) и по отсеву вкусовщины (пункт 7 раздела «Что осталось
|
||
непочиненным»).
|
||
|
||
### Метка `medium`, не `small`
|
||
|
||
Строка про `small` к этому прогону неприменима: `basics` отработал на глубине
|
||
«разбор», дома тем `security`, `operations` и `architecture` открывались на обоих
|
||
кругах.
|
||
|
||
---
|
||
|
||
## Можно ли мержить
|
||
|
||
**Кода, который сейчас неверен, я не нашёл ни на одном круге второго прохода.**
|
||
Приведение стоит на обеих точках, хвост имени отправителя наружу не выходит,
|
||
инвариант приватности из `CLAUDE.md` соблюдён, гейт красный только объявленным
|
||
долгом, `openspec validate --strict` — valid.
|
||
|
||
**Мержить можно после того, как человек закроет развилку №1** — она про то, чем
|
||
удержано верное поведение, а не про само поведение. Любой из трёх вариантов
|
||
развилки делает состояние мерджабельным; вариант (а) — самый дешёвый и требует
|
||
только переформулировать чек-лист `tasks.md` 2а.2, чтобы он не утверждал того,
|
||
чего нет.
|
||
|
||
Находки №2 и №3 — инлайн, вместе это одно слово в двух местах и одна строка в
|
||
тесте; мерджу они не мешают, но чинятся дешевле сейчас, чем потом.
|
||
|
||
Формулировка «критичных проблем не обнаружено» к этому прогону применима **только
|
||
вместе с секцией границ покрытия выше**, и главная её строка — `autotests` на
|
||
втором круге не запускался, а блокирующую находку нашёл я мутацией, а не проход.
|
||
|
||
---
|
||
---
|
||
|
||
# Приложение: отчёт первого круга, полностью, как был написан
|
||
|
||
> Ниже — текст триажа первого круга без правок. Исходы его находок проставлены
|
||
> в разделе «Что из починенного починено верно» выше: находка №1 закрыта
|
||
> решением контрольной точки (приведение метки), находки №2, №3 и №4 починены и
|
||
> удержаны мутациями.
|
||
|
||
## Сводка
|
||
|
||
- **Change:** `no-user-filename-in-log`. База диффа `origin/master`, судится
|
||
рабочее дерево (`git diff`): 4 файла, +172/−15.
|
||
- **Размер:** среднее. **Сложность:** знакомое. **Метка: medium** — максимум по
|
||
осям. Разметчик сам назвал спорным неприменение проектного триггера
|
||
«изменение, трогающее оба входа сразу»: правка лежит в единой точке
|
||
`createTranscribeJob`, а не отдельной работой по каждому входу. Человек на
|
||
чекпоинте согласился оставить `medium`.
|
||
- **Режим прогона:** по графу. Состав ревью кода: `autotests`, `specs`, `code`,
|
||
`basics`, `triage`.
|
||
- **Сигнал о заниженной метке:** `review-code` пришёл и метку заниженной **не**
|
||
считает. От `review-basics` сигнала в переданных мне выводах нет — ни «метка
|
||
верна», ни «занижена»; отличить «возражений нет» от «не сказано» по молчанию
|
||
нельзя, и я этого не делаю.
|
||
- **Гейт:** красный **только объявленным долгом**. Проверено мной самим, не со
|
||
слов прохода: `go build ./...`, `go vet ./...`, `gofmt -l .`, `go test ./...`
|
||
зелёные; `golangci-lint run` даёт ровно 4 знакомых замечания
|
||
(`speechkit.go:55`, `main.go:124`, `worker.go:51`, `transcribe.go:395`).
|
||
Смещение `394 → 395` внесено самим диффом. Новых красных шагов нет.
|
||
- **Находок на входе:** 17 (autotests 0 + 1 promote, specs 4, basics 6, code 6).
|
||
**После дедупликации по причине и отсева:** 4 в первых двух секциях, 3 в
|
||
гипотезах, 3 кандидата в правила.
|
||
|
||
### План разметки против исхода
|
||
|
||
| тема | дом | глубина | закрывает | исход |
|
||
| --- | --- | --- | --- | --- |
|
||
| requirements | `openspec/specs/intake/spec.md` + дельта change | разбор | specs | **закрыта**, 4 находки |
|
||
| autotests | `CLAUDE.md`, «Гейт» и «Команды» | — | autotests | **закрыта**, 0 находок + 1 promote |
|
||
| conventions | `docs/conventions/logging.md` + весь каталог | разбор | code | **закрыта**, 6 находок |
|
||
| architecture | `docs/architecture.md`, «Единые точки проекта» + `docs/passport.md` | разбор | basics | **закрыта**, ответ по вопросу темы + 1 находка |
|
||
| security | `docs/security.md`, «Куда уходит содержимое записи», «Из чего строятся пути и ключи» | разбор | basics | **закрыта**, 1 находка (главная) |
|
||
| operations | `docs/architecture.md`, «Эксплуатация» + `docs/database.md` | разбор | basics | **закрыта**, ответы по вопросам темы |
|
||
|
||
Тем без дома нет. Тем без отчёта нет. Своих тем проекта сверх ядра нет.
|
||
|
||
---
|
||
|
||
## Блокирует мердж
|
||
|
||
### 1. Дельта-спека письменно узаконивает произвольный текст отправителя, а хвост уходит на анонимный `/metrics`
|
||
|
||
- **Где:** `openspec/changes/no-user-filename-in-log/specs/intake/spec.md`
|
||
(«Расширение, взятое из этого имени, запретом не накрыто»);
|
||
`internal/service/transcribe.go:95` и `:143`; `internal/metrics/metrics.go:24`
|
||
и `:44`; `main.go:216`.
|
||
- **Severity:** critical. Инвариант `CLAUDE.md`: «Текст расшифровки, имя файла
|
||
пользователя и его сообщение в лог не пишутся — только длина и
|
||
идентификаторы. Нарушение необратимо».
|
||
- **Причина одна** на три носителя, поэтому это одна находка, а не три: `ext`
|
||
берётся из недоверенного имени дословно. Её нашли три прохода независимо
|
||
(`specs` #1, `basics` #1, `code` #1) — приоритет от этого выше, `confidence`
|
||
нет: под всеми проходами одна модель.
|
||
- **Оракул (построен и прогнан на этом прогоне):**
|
||
|
||
```
|
||
filepath.Ext("запись.тайное-слово") -> ".тайное-слово"
|
||
filepath.Ext("Разговор с Петровым 11.08") -> ".08"
|
||
filepath.Ext("отчёт.для Ивановой") -> ".для Ивановой"
|
||
```
|
||
|
||
Метка Prometheus отдаётся наружу дословно — программа с тем же выражением и
|
||
тем же `HistogramVec`, что в `metrics.go:24`, на `GET /metrics`:
|
||
|
||
```
|
||
transcriber_input_file_size_bytes_count{file_extension=".тайное-слово"} 1
|
||
```
|
||
|
||
Путь до этой строки достроен по коду: `header.Filename`
|
||
(`internal/controller/http/transcribe.go:43`) → `CreateJobFromApi` →
|
||
`createTranscribeJob` → `ext` (`:95`) → `WithLabelValues(ext)` (`:143`);
|
||
`router.GET("/metrics", gin.WrapH(promhttp.Handler()))` (`main.go:216`) стоит
|
||
за `sloggin` и `gin.Recovery` и ни за какой проверкой.
|
||
- **Чего эта находка НЕ утверждает:** «HTTP API открыт без аутентификации» —
|
||
типовое ложноположительное проекта (`docs/review.md`, «Типовые
|
||
ложноположительные»), и открытость `/metrics` записана в `docs/security.md`
|
||
строкой «Метрики и здоровье». Новое здесь не открытость, а то, что на эту
|
||
открытую поверхность попадает значение, которым распоряжается анонимный
|
||
отправитель, — и что дельта-спека это разрешает текстом.
|
||
- **Почему первое место в ранжировании:** ущерб необратим по букве инварианта
|
||
(строка уехала в собранные логи и в хранилище метрик), а канал шире того, что
|
||
задача закрыла: журнал читает владелец, `/metrics` — кто угодно из интернета.
|
||
Тем же концом это неограниченная кардинальность метрики, и множество значений
|
||
метки задаёт анонимный отправитель — этот исход вероятнее утечки осмысленного
|
||
слова и вредит эксплуатации.
|
||
- **Действие: развилка.**
|
||
|
||
> Хвост после последней точки в имени отправителя уходит дословно в журнал, в
|
||
> метку `file_extension` и в метку `source_format` на анонимный `/metrics`.
|
||
> Дельта-спека сейчас пишет это в канон фразой «расширение запретом не
|
||
> накрыто». Что делаем до мерджа?
|
||
>
|
||
> **(а)** Мержим как есть: остаток записан в модель угроз, нормализация уходит
|
||
> отдельной задачей. Канон при этом получает разрешающую фразу.
|
||
> **(б)** Мержим, сузив формулировку требования (например: в журнал и в метку
|
||
> идёт расширение из списка разрешённых, прочее заменяется на `.bin`), и в этой
|
||
> же задаче нормализуем **только значение метки метрики**. Имя файла на диске
|
||
> не трогается, раскладка `data/files` не меняется, правка локальна.
|
||
> **(в)** Нормализуем `ext` целиком в `createTranscribeJob`. Тогда меняется
|
||
> формат имени файла на диске — `CLAUDE.md` называет это необратимым и
|
||
> требующим отдельного решения человека, то есть возврата на чекпоинт.
|
||
|
||
### 2. Третий журнальный поток в оракуле ничем не удержан: снятие middleware не роняет ни одной проверки
|
||
|
||
- **Где:** `internal/controller/http/transcribe_test.go`, `setupTestEnv` и
|
||
`TestCreateTranscribeJob_SenderFileNameNotLogged`.
|
||
- **Severity:** major. Класс тот же, что проход `code` нашёл для стандартного
|
||
`log` и что уже починено; для потока middleware дефект остался.
|
||
- **Оракул (мутация на копии дерева, прогнана):** удаление
|
||
`router.Use(sloggin.New(logger))` из тестового роутера —
|
||
`ok git.vakhrushev.me/av/transcriber/internal/controller/http`, зелено. Для
|
||
сравнения, три другие мутации краснеют как заявлено: снятие `log.SetOutput`
|
||
роняет `…NotLoggedOnFailure`; имя под ключом `upload` роняет обе проверки
|
||
запрета; `Info → Debug` на строке приёма роняет `…JournalTracesRecord`.
|
||
- **Почему это важно, а не педантизм:** `design.md` включил middleware в
|
||
тестовый роутер ровно затем, чтобы квантор требования («ни одна журнальная
|
||
запись приёма») совпал с оракулом. Сегодня совпадение держится ничем: любой,
|
||
кто уберёт строку как лишнюю, сузит оракул критического инварианта молча.
|
||
Это записанное свойство проекта — `docs/review.md`, «Типовые узлы / Любой
|
||
узел»: «проверка способна упасть», и там же журнальная запись 2026-08-11 об
|
||
этом же классе.
|
||
- **Что удержит:** в перехваченном журнале есть строка middleware, вот она:
|
||
`msg="Incoming request" … request.path=/api/audio … response.status=201`.
|
||
Достаточно `require.Contains(journal, "Incoming request")` в проверке
|
||
успешного пути — рядом с уже стоящим `require.Contains(journal, "Err:")` в
|
||
проверке отказа.
|
||
- **Действие: инлайн.**
|
||
|
||
---
|
||
|
||
## Стоит исправить сейчас
|
||
|
||
### 3. Модель угроз называет метку `src_ext`, которой не существует
|
||
|
||
- **Где:** `docs/security.md:200` — «`file_extension` у размера принятой записи и
|
||
`src_ext` у длительности конвертации».
|
||
- **Оракул:** `internal/metrics/metrics.go:44` — метки
|
||
`{"source_format", "target_format", "error"}`. `grep -rn "src_ext"` по коду
|
||
отдаёт единственное совпадение: локальную переменную Go в
|
||
`internal/service/transcribe.go:194`. Журнальное поле рядом называется
|
||
`src_format`. Метки `src_ext` нет ни в одной метрике.
|
||
- **Почему сейчас:** эта строка — единственный носитель остатка в будущее.
|
||
Задача про нормализацию будет искать по имени метки и не найдёт её.
|
||
`file_extension` назван верно, ошибка ровно в одном слове.
|
||
- **Действие: инлайн.** Заменить `src_ext` на `source_format`.
|
||
|
||
### 4. Критерий рубрики «ровно одна запись приёма» оракулом не закрыт
|
||
|
||
- **Где:** `openspec/changes/no-user-filename-in-log/tasks.md`, критерий «в
|
||
буфере ровно одна запись приёма на принятую запись, её уровень `INFO`»;
|
||
проверка `TestCreateTranscribeJob_JournalTracesRecord`.
|
||
- **Что есть:** уровень теперь привязан к строке самого приёма и мутацией
|
||
проверен (`Info → Debug` краснеет). Числа записей не проверяет ничто.
|
||
- **Почему сейчас, а не в гипотезы:** это не догадка, а разрыв между отмеченным
|
||
как выполненный критерием и оракулом; закрывается одной строкой
|
||
`strings.Count(journal, "Creating transcribe job") == 1`. Заодно это сторож на
|
||
вторую строку приёма, в которую имя вернётся мимо нынешних проверок.
|
||
- **Действие: инлайн.**
|
||
|
||
---
|
||
|
||
## Гипотезы без доказательства
|
||
|
||
- **Запрет `t.Parallel()` держится комментарием.** `log.SetOutput` процессный, и
|
||
ничто, кроме текста рядом, не мешает следующей проверке в этом файле стать
|
||
параллельной; тогда перехват уедет к соседу. Оракула нет: воспроизвести это
|
||
можно только внесением `t.Parallel()`, то есть той самой будущей правкой.
|
||
`Confidence: medium`, severity не выше `minor`.
|
||
- **Позитивное утверждение `require.Contains(journal, "Err:")` привязано к
|
||
расхождению, которое конвенция объявляет подлежащим устранению** (`log.Printf`
|
||
в HTTP-транспорте мимо `slog`). Когда расхождение починят, проверка отказа
|
||
покраснеет по причине, не связанной с приватностью. Оракула нет — событие в
|
||
будущем. `minor`.
|
||
- **Кардинальность метки `file_extension` не замерена.** Утверждение «число
|
||
временных рядов растёт неограниченно» верно по построению, но роста никто не
|
||
мерил, а `docs/review.md` прямо велит не считать неизмеренный рост новой
|
||
находкой (строка про «файлы и объекты не удаляются»). Вес — только внутри
|
||
развилки №1.
|
||
|
||
---
|
||
|
||
## Promote candidates
|
||
|
||
- **Правило линтера на ключи `file_name`/`filename` в вызовах логгера**
|
||
(`sloglint` либо `forbidigo`). Пришло от `autotests`; `design.md` объявил это
|
||
non-goal задачи. Претензия на правило проекта, а не на этот код.
|
||
- **Свойство в `docs/review.md`, «Типовые узлы / Любой узел»:** каждый
|
||
перехваченный в оракуле поток журнала удерживается **своим** положительным
|
||
утверждением, иначе оракул сужается молча. Обобщение находки №2 и уже
|
||
починенной находки прохода `code`.
|
||
- **Нормализация расширения, взятого из имени отправителя** — отдельной задачей
|
||
в урожай (список разрешённых расширений, `.bin` для прочего), с явным решением
|
||
про имя файла на диске. Исход зависит от развилки №1.
|
||
|
||
---
|
||
|
||
## Что из уже починенного починено недостаточно
|
||
|
||
Проверено мутациями на копии дерева, не со слов.
|
||
|
||
- **Починено верно:** снятие `log.SetOutput` роняет проверку отказа; имя под
|
||
чужим ключом роняет обе проверки запрета; `Info → Debug` роняет проверку
|
||
прослеживаемости; утверждения о размере и уровне переписаны так, что
|
||
переживают смену обработчика на JSON (`size["=:\s]+%d` и
|
||
`level["=:\s]+INFO` разбирают и `size=33`, и `"size":33`); мёртвый
|
||
`log.SetFlags` снят; тестовый роутер собирается той же цепочкой, что боевой;
|
||
второй дом состава журнальной строки из `docs/conventions/logging.md` убран,
|
||
строка «*Расхождение:*» на месте.
|
||
- **Недостаточно — находка №2:** middleware в роутер добавлен, но не удержан;
|
||
фикс закрыл поток `log` и не закрыл поток, ради которого роутер и меняли.
|
||
- **Недостаточно — находка №3:** переписанный остаток в `docs/security.md`
|
||
называет несуществующую метку `src_ext`.
|
||
- **Недостаточно — находка №4:** критерий «ровно одна запись приёма» отмечен
|
||
выполненным, оракула у него нет.
|
||
- **Ослаблений не найдено:** ни одна прежняя проверка не стала слабее, `go test
|
||
./...` зелёный, новых замечаний линтера нет.
|
||
|
||
Отдельно подтверждаю утверждения, на которых стоит `design.md`, — проверял сам:
|
||
приём из Telegram имени, данного человеком, до сервиса не доводит
|
||
(`internal/controller/tg/tg.go:276` берёт `file.FilePath`, выданный Telegram;
|
||
`document.FileName` дальше проверки типа в `isAudioDocument` не идёт), а ключ
|
||
объекта в Object Storage строится после конвертации и всегда имеет вид
|
||
`идентификатор.ogg` (`internal/service/transcribe.go:190`).
|
||
|
||
---
|
||
|
||
## Границы покрытия
|
||
|
||
### План
|
||
|
||
Шесть тем, у каждой есть дом и глубина, все шесть перечислены в сводке выше с
|
||
исходом. Тем без дома нет, тем без отчёта нет. Своих тем проекта сверх ядра нет.
|
||
|
||
### Проходы
|
||
|
||
- **Запускались** на метке `medium`, режим «по графу»: `review-autotests`,
|
||
`review-specs` (код против спек), `review-code` (техника и конвенции, глубина
|
||
разбор), `review-basics` (темы `security`, `operations`, `architecture`,
|
||
глубина разбор), `review-triage`.
|
||
- **Не запускались:** проходы ревью дизайна (`specs`, `rubric`) — они
|
||
отработали до кода, на этапе предложения, и в этот прогон не входят.
|
||
- **Чего в конвейере нет вовсе** — четыре строки, которые не принесёт ни один
|
||
проход:
|
||
1. **Решения проекта не сверялись.** `docs/adr/` — процессный документ, прогон
|
||
его не открывает. Расхождение изменения с записанным решением ловит сверка
|
||
документации (`av-dev-docs:healthcheck`), а не ревью.
|
||
2. **Записанные наблюдения проекта не использовались.** `docs/research/` —
|
||
тоже процессный. Все числа в этом отчёте сняты на этом прогоне, команды
|
||
приложены.
|
||
3. **Поимённая сверка с руководством по стилю Go не задавалась ни одним
|
||
проходом.** Различение «идиоматично против распространено» не спрашивал
|
||
никто.
|
||
4. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера
|
||
нет.** Проход независимой реализации снят по стоимости; «не знаю, чего не
|
||
знаю» никто не достаёт.
|
||
|
||
### Чего запущенные проходы не могли проверить в принципе
|
||
|
||
- Ни один проход не гонял сервис на настоящих данных: `testdata` в проекте нет
|
||
по запрету `CLAUDE.md`, реальные ключи Yandex Cloud под запретом, боевую БД и
|
||
`data/files` трогать нельзя. Все оракулы — синтетический вход и мутации.
|
||
- `autotests` судит прогон и мутации, а не смысл проверок; `specs` судит код
|
||
против заказанного поведения и не ищет дефектов вне него; `code` судит технику
|
||
и конвенции и не судит требования; `basics` идёт по трём темам и только по их
|
||
записанным домам.
|
||
- Приём из Telegram своей проверки не получил вовсе (объявлено риском в
|
||
`design.md`): правка достаётся ему общим шагом кода, но оракула на него нет.
|
||
- Триаж не читает код в поисках дефектов: пропуск любого прохода — мой пропуск
|
||
тоже.
|
||
|
||
### Что осталось целиком на человеке
|
||
|
||
Из `docs/review.md`, «Недоступно проверке», двумя списками — они не сливаются.
|
||
|
||
**Не проверит ни один проход:**
|
||
|
||
- `operations`: поведение SpeechKit и Object Storage под нагрузкой и на границах
|
||
— поднять их в тесте нечем;
|
||
- `operations`: реальный профиль нагрузки; проект живёт на единицах записей в
|
||
день, и утверждения о росте остаются условиями;
|
||
- `security`: стойкость `ffmpeg` к вредоносному входу — разбор чужого формата
|
||
отдан внешней программе.
|
||
|
||
**Перестали проверять сознательно:**
|
||
|
||
- `autotests`: разбор вывода настоящего `ffprobe`. Проверки приёма получают
|
||
длительность от подставного источника; своего теста у
|
||
`adapter/metaviewer/ffmpeg` нет. Решение и его цена —
|
||
`docs/adr/ADR-2026-08-11-stub-adapters-in-tests.md`.
|
||
|
||
**Плюс общее, вне зависимости от проекта:** история инцидентов, поведение под
|
||
реальным потоком, поведение внешних систем в их версиях, завязка потребителей на
|
||
текущее поведение и вопрос «а нужна ли эта функциональность вообще».
|
||
|
||
### Каких документов проекта не хватило
|
||
|
||
- **`CLAUDE.md`, «Ориентир по размеру порции: не замерялся».** Разметка
|
||
«инлайн против развилки» опирается на right-size, а мерки right-size в проекте
|
||
нет. Пометки «инлайн» в находках 2–4 поставлены по объёму правки (одна-две
|
||
строки), и это моё предположение, а не сверка с записанным ориентиром.
|
||
- **Дома у вопроса «что можно класть в метку метрики» нет.** `docs/security.md`
|
||
описывает открытость `/metrics`, но правила состава меток нет ни в модели
|
||
угроз, ни в конвенциях; отсюда развилка вместо однозначного вердикта в находке
|
||
№1.
|
||
- Прочие нужные документы на месте и использованы: инварианты `CLAUDE.md`,
|
||
`docs/review.md` (включая «Типовые ложноположительные» — применён пункт про
|
||
открытый HTTP API), `docs/security.md`, `docs/conventions/logging.md`.
|
||
Деградации по ним нет.
|
||
|
||
### Сработавшие потолки
|
||
|
||
- **Ни один из четырёх проходов не сообщил свой потолок** — ни сколько находок
|
||
показал из скольких, ни что осталось за срезом. Это находка о прогоне: судить,
|
||
полон ли вход триажа, нечем, и «на входе 17 находок» может означать «17 из
|
||
17», а может «17 из скольких-то».
|
||
- **Потолок триажа сработал мягко:** 2 из 3 в «Блокирует мердж» и 2 из 4 в
|
||
«Стоит исправить сейчас». Ничего не выброшено из-за потолка; выброшенное
|
||
выброшено по дедупликации (одна причина на три носителя в находке №1, один
|
||
класс на четыре формулировки в находке №2) и по отсеву — снятый `log.SetFlags`
|
||
и второй дом в конвенции уже починены, а «изоляция буфера держится не тем, чем
|
||
заявлено» после правки сведено к комментарию и уехало в гипотезы.
|
||
|
||
Формулировка «критичных проблем не обнаружено» к этому прогону неприменима:
|
||
критичная проблема обнаружена и стоит развилкой №1.
|