имя файла отправителя убрано из журнала приёма
- расширение приводится к перечню известных форматов прежде метки метрики: страница метрик открыта, и хвост имени уезжал на неё дословно - проверки приёма перехватывают все три потока журнала и читают реестр метрик, каждая падает при снятии того, что сторожит
This commit is contained in:
@@ -0,0 +1,802 @@
|
||||
# Триаж ревью кода: `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.
|
||||
Reference in New Issue
Block a user