хранилище, файлы записей и очередь переведены на встроенную PocketBase
- записи, метаданные и файлы съехались под один каталог данных; появилась панель владельца, а gin, goqu, goose и требование CGO ушли - захват задачи стал одним запросом с RETURNING; заведены число попыток, состояние dead и нарастающая пауза вместо признака is_error - имя файла в хранилище задаёт сервис и в журнал не идёт: вместе с идентификатором записи оно собирало бы ссылку на скачивание
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# Отчёт ревью — change `pocketbase-storage`
|
||||
|
||||
Метка `large`, режим «по графу». Составил проход `review-triage`; файл записал
|
||||
оркестратор — среда запрещает подагенту писать отчётные `.md`. Раздел «Исход по
|
||||
находкам» дописан оркестратором после отработки и помечен как таковой.
|
||||
|
||||
## Сводка
|
||||
|
||||
- **Размер:** крупное. **Сложность:** незнакомое. **Метка:** `large` — максимум
|
||||
по обеим осям. Триггеры `docs/review.md`: «замена хранилища или переход на
|
||||
PocketBase — любой её кусок», «смена модели очереди», «изменение, трогающее оба
|
||||
входа сразу».
|
||||
- **Гейт:** зелёный, проверен триажем — `task gate`, exit 0, восемь шагов.
|
||||
- **Особенность прогона:** часть находок первой волны оркестратор починил до
|
||||
запуска `ops`. Каждую пометку «ПОЧИНЕНО» триаж проверял в коде.
|
||||
|
||||
### Сигнал о заниженной метке
|
||||
|
||||
Не пришёл. `review-code` возражений не заявил; `review-basics` на этой метке не
|
||||
запускался. Это одно молчание и одно отсутствие, а не подтверждение двумя
|
||||
источниками.
|
||||
|
||||
### План разметки задачи с исходом по каждой теме
|
||||
|
||||
| тема | дом | глубина | кто закрывает | исход |
|
||||
|---|---|---|---|---|
|
||||
| requirements | `openspec/specs/` + дельты | разбор | `specs` | **закрыта**, 6 находок |
|
||||
| autotests | `CLAUDE.md`, «Гейт», «Инварианты» | — | `autotests` | **закрыта**, 3 находки |
|
||||
| conventions | `docs/conventions/` | разбор | `code` | **закрыта**, 9 находок + 3 за срезом потолка |
|
||||
| architecture | `docs/architecture.md` + `passport.md` | доказательство | `architecture` | **закрыта**, 7 находок |
|
||||
| security | `docs/security.md` | доказательство | `adversary` | **закрыта**, 5 находок |
|
||||
| operations | `docs/architecture.md` «Эксплуатация» + `database.md` | доказательство | `ops` | **закрыта**, 3 находки + 3 замера |
|
||||
|
||||
**Темы без отчёта нет.** `basics` не запускался по условию состава при метке
|
||||
`large` — темы он не уносил.
|
||||
|
||||
Отдельно: результатов **ревью дизайна** на вход триажа не подавали. Сошлись ли
|
||||
ответы автора о трёх формах решения с построенным — на этом прогоне не проверено
|
||||
никем.
|
||||
|
||||
### Счёт находок
|
||||
|
||||
36 пунктов от шести проходов → 27 причин после дедупликации → 12 починено до
|
||||
`ops` (одна частично), 1 снята как неверная, 14 осталось плюс 1 новая, вскрытая
|
||||
проверкой починок. В отчёте: 3 блокирующих, 3 «исправить сейчас», 6 гипотез,
|
||||
3 promote.
|
||||
|
||||
### Проверка починок на месте
|
||||
|
||||
| находка | чем проверено | вердикт |
|
||||
|---|---|---|
|
||||
| `MaxSize: 0` = 5 МиБ (specs-1 = code-1 = adversary-2) | `entity.MaxRecordSize = 8 << 30` у поля файла и у тела приёма; тест судит границу `5<<20 + 1` и способен упасть | полна |
|
||||
| хук панели правил записи конвейера (specs-2 = code-2 = adversary-3 = architecture-1) | `OnRecordUpdateRequest`; два теста, включая обратный | полна в объявленном объёме; остаток — блокер 2 |
|
||||
| 413 на 32 МиБ и `ReadTimeout` 5 минут | `apis.BodyLimit(entity.MaxRecordSize)` на маршруте, `ReadTimeout = 0` | полна |
|
||||
| гонка `srv` | буферизованный канал, чтение `select`/`default` | полна |
|
||||
| `file` без `Required` | `Required: true`, шаг схемы на сервер не уезжал | полна |
|
||||
| спека утверждала про суффикс имени | прогон: имя в хранилище — ровно заданное, суффикса нет | полна |
|
||||
| рабочая копия без расширения | `Stage(ext string, …)`, приём передаёт расширение | полна |
|
||||
| инвариант «колонка в N местах» | возвращён в `CLAUDE.md` с перечнем мест | полна |
|
||||
| две записи ERROR на один отказ приёма | лог из обработчика снят | полна |
|
||||
| расхождения с `conventions/database.md` | обе строки на месте | полна |
|
||||
| **вечная ссылка на чужое аудио через журнал** | прогон: `Open` и `Localize` отдают текст с идентификатором записи, имени файла в нём нет | путь А закрыт; путь Б закрыт наполовину — см. «Стоит исправить», п. 2 |
|
||||
| пустой держатель в `Save` | комментарий исправлен, ветка оставлена | частично, и этого достаточно |
|
||||
|
||||
**Снято как неверное:** замечание `architecture` про ключ объекта в Object
|
||||
Storage. Оно стояло на посылке о суффиксе имени, которой нет: имя в хранилище и
|
||||
есть `<uuid><расширение>`, значит утверждение `architecture.md` верно.
|
||||
|
||||
## Блокирует мердж
|
||||
|
||||
### 1. Образ не собирается: сборщик `golang:1.24`, а модуль требует Go 1.25
|
||||
|
||||
- Файл: `Dockerfile:2`, `go.mod:3`
|
||||
- Severity: major, Confidence: high
|
||||
- Оракул: сборка в контейнере `golang:1.24-alpine` → `go.mod requires go >= 1.25.0
|
||||
(running go 1.24.13; GOTOOLCHAIN=local)`; на `golang:1.25-alpine` → успех.
|
||||
Директива не наша: `pocketbase@v0.39.10/go.mod` объявляет `go 1.25.0`.
|
||||
- Последствие: `task image` падает — выкладки change нет вовсе. Гейт этого не
|
||||
показывает: образ он не собирает, а `go build` идёт на хостовом Go.
|
||||
- Найдено: **никем** — вскрылось проверкой починок (триаж).
|
||||
- Действие: инлайн
|
||||
|
||||
### 2. Правка задачи в панели пропадает при ближайшем сохранении шага
|
||||
|
||||
- Файл: `transcript_job_repo.go`, `job_mapping.go`, `panel.go`
|
||||
- Severity: major, Confidence: high
|
||||
- Оракул: временный тест — задача с `tg_chat_id = 111` захвачена шагом, правится
|
||||
через `PATCH /api/collections/transcribe_jobs/records/<id>`, затем шаг
|
||||
сохраняет снимок. Итог: `expected 999999 / actual 111`.
|
||||
- Последствие: `Save` сторожил только `acquisition_id`, а запись клала **все**
|
||||
поля снимком с момента захвата. Окно — до восьми часов. Ни строки в журнале,
|
||||
ни отказа в панели. Панель ради правки и покупалась.
|
||||
- Найдено: `ops`.
|
||||
- Действие: развилка
|
||||
|
||||
### 3. Два из трёх шагов конвейера переписаны и не выполнены ни одним тестом
|
||||
|
||||
- Файл: `internal/service/transcribe.go` — `transcribeJob`, `checkTranscribeJob`,
|
||||
`completeJob`
|
||||
- Severity: major, Confidence: high
|
||||
- Оракул: покрытие с `-coverpkg=./...` — 0.0% у всех трёх и у обоих
|
||||
`FindAndRun*`; `git diff --stat` по файлу — 268 вставок, 192 удаления.
|
||||
- Последствие: путь «отдать запись на распознавание → дождаться операции →
|
||||
ответить пользователю» прошёл замену хранилища без единого выполнения.
|
||||
- Найдено: `autotests`.
|
||||
- Действие: инлайн
|
||||
|
||||
## Стоит исправить сейчас
|
||||
|
||||
### 1. Документы обещают, что рабочую копию убирает хранилище; убирает вызывающий
|
||||
|
||||
- Severity: minor, Confidence: high. Найдено: `architecture`. Действие: развилка.
|
||||
- `architecture.md`, `design.md` и дельта-спека `storage` требуют, чтобы уборку
|
||||
делало хранилище. В коде копию закрывает вызывающий тремя `defer`. Дефекта в
|
||||
коде нет — расхождение в записи, которая уедет в архив.
|
||||
|
||||
### 2. Отказ заливки в Object Storage больше не отличим от любого другого
|
||||
|
||||
- Severity: minor, Confidence: high. Найдено: **никем** — вскрылось проверкой
|
||||
полноты починки. Действие: инлайн.
|
||||
- Починка приватности оборвала цепочку `%w` целиком: отозванные ключи,
|
||||
исчезнувший бакет и отказ сети дают одну строку.
|
||||
|
||||
### 3. Входящие HTTP-запросы исчезли из журнала процесса
|
||||
|
||||
- Severity: minor, Confidence: high. Найдено: `code`. Действие: развилка.
|
||||
- `sloggin` ушёл вместе с gin; `activityLogger` PocketBase пишет в свою таблицу.
|
||||
Строка «*Расхождение:*» в `logging.md` указывает на удалённую библиотеку.
|
||||
|
||||
## Гипотезы без доказательства
|
||||
|
||||
1. Ответ SpeechKit об отказе операции может нести S3-URI и уехать в журнал и в
|
||||
`error_text`. Оракул требует настоящего отказа Yandex — запрещено `CLAUDE.md`.
|
||||
2. Имя файла в хранилище принимает почти любой хвост имени отправителя. Выхода за
|
||||
каталог **нет** (15 враждебных имён). Станет находкой с появлением экрана
|
||||
списка записей.
|
||||
3. `entity.File.FileName` несёт два смысла — имя в хранилище и ключ объекта.
|
||||
Сегодня они совпадают дословно.
|
||||
4. Отмена контекста структурно невозможна: `RunInTransaction`/`FindRecordById`
|
||||
контекста не принимают. Замер: под внешней блокировкой захват держится 9.53 с
|
||||
при `busy_timeout=10000`. Уточнение уже объявленного долга.
|
||||
5. Таймаутов у Telegram, S3 и SpeechKit по-прежнему нет. Задача заведена.
|
||||
6. Приём пишет тело на диск дважды. Не замерено; нагрузка объявлена вне модели.
|
||||
|
||||
Ниже потолка названы и не потеряны: `LostAcquisitionError` типом там, где
|
||||
конвенция просит sentinel; форма обёрток `"failed to <действие>: %w"`; уровень
|
||||
`ERROR` с инкрементом метрики на потерянном захвате против `WARN` конвенции;
|
||||
панель как третий вход в таблицу задач.
|
||||
|
||||
## Promote candidates
|
||||
|
||||
1. **`govulncheck ./...` шагом гейта.** Сегодня его нет ни в гейте, ни в списке
|
||||
«чего в гейте намеренно нет». Достижимых уязвимостей две, унаследованных:
|
||||
`GO-2026-6061` (grpc), `GO-2026-5764` (aws eventstream, s3).
|
||||
2. **Сверка версии Go в `Dockerfile` с директивой `go` в `go.mod`.** Блокер 1
|
||||
прошёл весь конвейер молча именно потому, что образ никто не собирает.
|
||||
3. **Покрытие изменённых строк.** Блокер 3 — второй случай подряд, когда его
|
||||
ловят руками.
|
||||
|
||||
## Границы покрытия
|
||||
|
||||
- Запускались: `specs`, `code`, `architecture`, `adversary`, `autotests` — по
|
||||
коду **до** починок первой волны; `ops` — по коду **после**. Находки пяти
|
||||
первых перепроверены триажем поимённо.
|
||||
- `basics` не запускался: при метке `large` темы разобраны именными проходами,
|
||||
своих тем проекта нет.
|
||||
- Независимой реализации для сравнения не строил никто — прохода нет в конвейере.
|
||||
- Поимённая сверка с руководствами по стилю Go не задавалась ни одним проходом.
|
||||
Для change, который впервые в проекте пишет на PocketBase, это самый дорогой
|
||||
пробел.
|
||||
- **Блоки `Coverage of this pass` до триажа дошли не от всех проходов.** О своих
|
||||
потолках сообщил только `code` (конвенционная половина, 4, за срезом три
|
||||
находки — перенесены). Остальные не сообщили; это находка о прогоне.
|
||||
- Решения проекта (`docs/adr/`) и записанные наблюдения (`docs/research/`) прогон
|
||||
не открывает — процессные документы. Расхождение change с записанным решением
|
||||
ловит не ревью, а сверка документации (`av-dev-docs:healthcheck`).
|
||||
- Не проверит ни один проход: поведение SpeechKit и Object Storage под нагрузкой
|
||||
и на границах; реальный профиль нагрузки; стойкость `ffmpeg` к вредоносному
|
||||
входу.
|
||||
- Перестали проверять сознательно: разбор вывода настоящего `ffprobe` — решение и
|
||||
цена в `ADR-2026-08-11-stub-adapters-in-tests.md`.
|
||||
- Каких документов не хватило: `docs/conventions/web-ui.md` описывает будущую
|
||||
SPA, а не панель — судить панель против конвенции было нечем; `docs/research/`
|
||||
по весу шестичасовой записи пуст, оценки читались как оценки.
|
||||
|
||||
## Исход по находкам
|
||||
|
||||
*Раздел дописан оркестратором после отработки урожая; в отчёте триажа его нет.*
|
||||
|
||||
**Починено:**
|
||||
|
||||
- блокер 1 — `Dockerfile` переведён на `golang:1.25-alpine`, строки про Go 1.24 в
|
||||
`CLAUDE.md` и `README.md` исправлены; сборка в контейнере проверена;
|
||||
- блокер 2 — запись шага разделена: `applyOwnedByPipeline` кладёт только поля
|
||||
конвейера, `applyToRecord` целиком остаётся заведению. Заведён тест
|
||||
`TestSave_KeepsOwnerEditMadeWhileStepHeldTheJob`; норма дописана в дельта-спеку
|
||||
`pipeline` со сценарием;
|
||||
- блокер 3 — заведён управляемый двойник распознавателя и восемь проверок
|
||||
(`internal/service/recognition_test.go`). Покрытие: `transcribeJob` 0 → 67.6%,
|
||||
`checkTranscribeJob` 0 → 72.4%, `completeJob` 0 → 100%, оба `FindAndRun*` → 100%;
|
||||
- «стоит исправить» 2 — класс отказа SDK сохраняется через `smithy.APIError`,
|
||||
адрес объекта — нет;
|
||||
- «стоит исправить» 3 — журнал входящих запросов вернулся своим слоем в `main.go`
|
||||
с пятью полями конвенции, `/health` и `/metrics` на `DEBUG`; расхождение в
|
||||
`logging.md` снято.
|
||||
|
||||
**Решено развилкой, вариант назван человеку на чекпоинте:**
|
||||
|
||||
- «стоит исправить» 1 — норму привели к коду: хранилище даёт единственный способ
|
||||
убрать копию, зовёт его шаг. Замкнуть уборку на репозиторий мешает конвертация:
|
||||
ей нужны две копии разом. Цена названа — норма держится проверками, и проверки
|
||||
заведены на приём и на шаг конвертации.
|
||||
|
||||
**Оставлено, передано урожаем:** все шесть гипотез и четыре находки ниже потолка;
|
||||
три кандидата в promote.
|
||||
Reference in New Issue
Block a user