заведены задачи из урожая ревью и заметок владельца
This commit is contained in:
@@ -53,3 +53,12 @@
|
|||||||
- [🧹 Разобрать мелочи http-транспорта](items/http-transport-nits.md) — Маршруты зарегистрированы дважды, и переименование пути в main.go проходит проверки зелёным; обработчик пишет в журнал через стандартный log и дублирует запись, уже сделанную сервисом.
|
- [🧹 Разобрать мелочи http-транспорта](items/http-transport-nits.md) — Маршруты зарегистрированы дважды, и переименование пути в main.go проходит проверки зелёным; обработчик пишет в журнал через стандартный log и дублирует запись, уже сделанную сервисом.
|
||||||
- [🧹 Переименовать образец конфига в config.example.toml](items/config-example-toml.md) — Конвенция называет config.dist.toml объявленным расхождением, но тут же пишет это имя как правило — документ противоречит сам себе, а образец расходится с конвенцией.
|
- [🧹 Переименовать образец конфига в config.example.toml](items/config-example-toml.md) — Конвенция называет config.dist.toml объявленным расхождением, но тут же пишет это имя как правило — документ противоречит сам себе, а образец расходится с конвенцией.
|
||||||
- [🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus](items/opentelemetry-fit.md) — Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
- [🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus](items/opentelemetry-fit.md) — Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
||||||
|
- [🧹 Обновить Go до 1.26 в модуле и в образе](items/go-1-26-upgrade.md) — Модуль объявляет go 1.25.0, образ собирается на golang:1.25-alpine, а на машине разработки стоит 1.26.5: три места расходятся, и проверки идут не на той версии, что уезжает на сервер.
|
||||||
|
- [🧹 Ловить уязвимости в зависимостях шагом гейта](items/gate-dependency-vulnerabilities.md) — govulncheck находит две достижимые уязвимости в клиентах Yandex, а ни гейт, ни список «чего в гейте нет» о нём не знают: узнать о третьей будет неоткуда.
|
||||||
|
- [🧹 Сверять версию Go в образе с директивой go.mod](items/gate-go-version-sync.md) — Образ гейт не собирает, и расхождение версий прошло весь конвейер молча: task image упал бы уже на сервере.
|
||||||
|
- [🧹 Считать покрытие изменённых строк шагом гейта](items/gate-changed-lines-coverage.md) — Свойство «изменённое место покрыто хоть одним тестом» записано в docs/review.md, но не механизировано: за две задачи подряд непокрытые шаги ловили руками.
|
||||||
|
- [🧹 Прервать шаг конвейера отменой контекста](items/context-cancel-in-pipeline.md) — Воркер читает ctx только между итерациями: остановка контейнера ждёт конца шага, а на занятом писателе один запрос к хранилищу держится до 9,5 секунды при мягком таймауте в 5.
|
||||||
|
- [🧹 Разобрать мелочи слоя хранилища](items/storage-layer-nits.md) — Три мелочи ниже потолка триажа: цикл воркера пишет потерю захвата уровнем ERROR и считает её отказом, тип ошибки заведён там, где конвенция просит sentinel, а FileName несёт два разных смысла.
|
||||||
|
- [🔬 Уведомление SpeechKit о готовности вместо опроса](items/speechkit-callback-fit.md) — Шаг проверки дёргает операцию раз в 5 секунд всё время распознавания: часовая запись даёт порядка 720 обращений к платному сервису вместо одного ответа.
|
||||||
|
- [🔬 Адрес объекта в тексте отказа SpeechKit](items/speechkit-error-text-leak.md) — Текст отказа операции приходит от Yandex и уезжает в журнал и в колонку error_text: если он несёт URI объекта, из журнала снова собирается ссылка на чужую запись.
|
||||||
|
- [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# 🧹 Прервать шаг конвейера отменой контекста
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Воркер читает ctx только между итерациями: остановка контейнера ждёт конца шага, а на занятом писателе один запрос к хранилищу держится до 9,5 секунды при мягком таймауте в 5.
|
||||||
|
|
||||||
|
Долг назван в `docs/architecture.md` — раздел про capability `pipeline`, где
|
||||||
|
отмена контекста посреди шага перечислена среди неописанного. Ревью задачи
|
||||||
|
`pocketbase-storage` 2026-08-12 его уточнило, и уточнение меняет способ починки:
|
||||||
|
**передать `ctx` по всему коду недостаточно**. Хранилище не принимает контекста ни в
|
||||||
|
одном из методов записи — ни в сохранении записи, ни в транзакции, ни в чтении по
|
||||||
|
идентификатору; отменять там нечем без перехода части записи на сырые запросы,
|
||||||
|
у которых контекст есть.
|
||||||
|
|
||||||
|
Число снято на прогоне: под внешней эксклюзивной блокировкой файла базы захват
|
||||||
|
задачи держится 9,53 секунды (таймаут занятости хранилища — 10 секунд) и
|
||||||
|
возвращает отличимый отказ, а не «работы нет».
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- цикл воркера `internal/controller/worker`;
|
||||||
|
- сигнатуры шагов конвейера в `internal/service`;
|
||||||
|
- договор ядра с хранилищем `internal/contract` и его реализация в
|
||||||
|
`internal/adapter/repo/pocketbase`;
|
||||||
|
- таймауты мягкой и жёсткой остановки — конфиг, `[server] shutdown_timeout` и
|
||||||
|
`force_shutdown_timeout`;
|
||||||
|
- спека `pipeline`: сегодня раздел `Purpose` объявляет отмену контекста
|
||||||
|
неописанной.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Шаг, идущий дольше мягкого таймаута, обрывается по сигналу остановки. Оракул —
|
||||||
|
тест с шагом, спящим дольше таймаута: остановка укладывается в бюджет, а не
|
||||||
|
доживает до жёсткой.
|
||||||
|
- Оборванный шаг оставляет задачу пригодной к повтору. Оракул — тот же тест:
|
||||||
|
после остановки задача не захвачена и выдаётся следующему.
|
||||||
|
- Отмена не путается с отказом: прерванный шаг не тратит попытку и не уходит в
|
||||||
|
«мертва». Оракул — тест на трёх прерываниях подряд, счётчик попыток не растёт
|
||||||
|
до предела.
|
||||||
|
- Спека `pipeline` описывает отмену, и раздел `Purpose` её больше не числит
|
||||||
|
неописанной. Оракул — `openspec validate --strict`.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Таймаут занятости хранилища этой задачей не трогаем: он свойство библиотеки, а не
|
||||||
|
наше. Мягкая остановка в 5 секунд может оказаться меньше неотменяемого минимума —
|
||||||
|
тогда число меняется, и это решение человека.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Направления
|
- **Секция:** Направления
|
||||||
- **Зачем:** Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда.
|
- **Зачем:** Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Сервис объявлен архивом 2026-08-11, и с тем же решением у человека появляется
|
Сервис объявлен архивом 2026-08-11, и с тем же решением у человека появляется
|
||||||
обратное право: сказать «убери это» и убедиться, что убрано. Речь в записи
|
обратное право: сказать «убери это» и убедиться, что убрано. Речь в записи
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 🧹 Считать покрытие изменённых строк шагом гейта
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Свойство «изменённое место покрыто хоть одним тестом» записано в docs/review.md, но не механизировано: за две задачи подряд непокрытые шаги ловили руками.
|
||||||
|
- **Теги:** question
|
||||||
|
|
||||||
|
`CLAUDE.md`, раздел «Гейт», объявляет прямо: «покрытие изменённых строк не
|
||||||
|
считается ничем». Цена этого измерена дважды. В задаче
|
||||||
|
`http-handler-tests-never-green` тесты обработчика не были зелёными ни разу; в
|
||||||
|
`pocketbase-storage` два из трёх шагов конвейера переписали целиком и не
|
||||||
|
выполнили ни одним тестом — нашёл это проход ревью, а не машина.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- набор шагов `task gate` в `Taskfile.yml` и переменная `BASE` как база диффа;
|
||||||
|
- семантика гейта в `CLAUDE.md`, раздел «Гейт», строка про покрытие;
|
||||||
|
- `docs/review.md`, раздел настройки конвейера: чем проход `autotests` перестаёт
|
||||||
|
заниматься руками.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Изменённая строка без покрытия роняет гейт. Оракул — прогон на дереве, где в
|
||||||
|
тронутый файл добавлена заведомо невыполняемая ветка: шаг краснеет с её
|
||||||
|
адресом.
|
||||||
|
- Изменение, не трогающее код, шаг не гоняет. Оракул — `task gate` на дереве с
|
||||||
|
правкой одной только документации: шаг сообщает о пропуске с причиной.
|
||||||
|
- Порог назван числом в `CLAUDE.md` и совпадает с тем, что проверяет шаг. Оракул
|
||||||
|
— `task gate`, шаг `docs.py check`.
|
||||||
|
|
||||||
|
## Вопросы
|
||||||
|
|
||||||
|
- **Какой порог и что считать изменённой строкой.** Варианты: сто процентов
|
||||||
|
изменённых строк (роняет на всякой ветке отказа, которую нечем изобразить),
|
||||||
|
порог ниже ста (число берётся из ниоткуда), только новые функции целиком.
|
||||||
|
Цена решения — частота ложных отказов гейта; пока решения нет, шаг заводить
|
||||||
|
нельзя.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Общее покрытие проекта не считаем и порога на него не ставим: он растёт от
|
||||||
|
тестов на тривиальное и не отвечает ни на один вопрос.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 🧹 Ловить уязвимости в зависимостях шагом гейта
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** govulncheck находит две достижимые уязвимости в клиентах Yandex, а ни гейт, ни список «чего в гейте нет» о нём не знают: узнать о третьей будет неоткуда.
|
||||||
|
|
||||||
|
Обе найдены ревью задачи `pocketbase-storage` 2026-08-12 и достижимы из кода
|
||||||
|
распознавателя: `GO-2026-6061` в `google.golang.org/grpc` (исправлено в v1.82.1)
|
||||||
|
и `GO-2026-5764` в `aws-sdk-go-v2` — `aws/protocol/eventstream` (в v1.7.8) и
|
||||||
|
`service/s3` (в v1.97.3). Ни одну из них эта задача не вносила: `git diff
|
||||||
|
origin/master -- go.mod` их версий не касался.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- версии `google.golang.org/grpc`, `aws-sdk-go-v2/service/s3` и
|
||||||
|
`aws-sdk-go-v2/aws/protocol/eventstream` в `go.mod`;
|
||||||
|
- набор шагов `task gate` в `Taskfile.yml`;
|
||||||
|
- семантика гейта в `CLAUDE.md` — раздел «Гейт», список «что красит безусловно»
|
||||||
|
либо «чего в гейте намеренно нет».
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Обе объявленные уязвимости из кода недостижимы. Оракул — `govulncheck ./...`
|
||||||
|
печатает «No vulnerabilities found» либо оставляет только недостижимые.
|
||||||
|
- Новая достижимая уязвимость роняет гейт. Оракул — прогон `task gate` на
|
||||||
|
дереве с намеренно откаченной версией `grpc`: шаг краснеет с именем уязвимости.
|
||||||
|
- Место шага названо в `CLAUDE.md`: либо в перечне красящих безусловно, либо в
|
||||||
|
«чего в гейте нет» с указанием, кто тогда обязан его гонять. Оракул —
|
||||||
|
`task gate`, шаг `docs.py check`.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Обновление версий трогает клиентов Yandex — прогон на реальных ключах запрещён,
|
||||||
|
подставляй `internal/adapter/recognizer/memory.go`. Решение, ронять ли гейт на
|
||||||
|
уязвимости без исправления, принимает человек.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 🧹 Сверять версию Go в образе с директивой go.mod
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Образ гейт не собирает, и расхождение версий прошло весь конвейер молча: task image упал бы уже на сервере.
|
||||||
|
|
||||||
|
Случай записан журналом дефектов `docs/review.md` за 2026-08-12: `go mod tidy`
|
||||||
|
поднял директиву до 1.25, `Dockerfile` остался на `golang:1.24-alpine` с
|
||||||
|
`GOTOOLCHAIN=local`, и шесть проходов ревью вместе с восемью шагами гейта
|
||||||
|
показали зелёное. Нашлось случайным свойством прогона, а не устройством
|
||||||
|
проверок.
|
||||||
|
|
||||||
|
Сборку образа в гейт не заводим — она дорога. Дешёвая замена: сравнение двух
|
||||||
|
строк.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- набор шагов `task gate` в `Taskfile.yml`;
|
||||||
|
- директива `go` в `go.mod` и сборочный слой `Dockerfile` как источники
|
||||||
|
сравнения;
|
||||||
|
- семантика гейта в `CLAUDE.md`, раздел «Гейт».
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Расхождение версий роняет гейт. Оракул — прогон `task gate` на дереве, где
|
||||||
|
версия в `Dockerfile` понижена на минор: шаг краснеет и называет оба числа.
|
||||||
|
- Совпадение гейт не роняет. Оракул — `task gate` на неизменённом дереве.
|
||||||
|
- Шаг не требует docker и работает без сети. Оракул — прогон с
|
||||||
|
`DOCKER_HOST=/dev/null`.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Сравнение строковое: `Dockerfile` называет `golang:<major>.<minor>-alpine`, а
|
||||||
|
`go.mod` — `<major>.<minor>.<patch>`. Сверяются мажор и минор; патч сборочного
|
||||||
|
образа своей жизнью живёт законно.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 🧹 Обновить Go до 1.26 в модуле и в образе
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Модуль объявляет go 1.25.0, образ собирается на golang:1.25-alpine, а на машине разработки стоит 1.26.5: три места расходятся, и проверки идут не на той версии, что уезжает на сервер.
|
||||||
|
|
||||||
|
Заметка владельца от 2026-08-12. Версия 1.25 в модуле появилась не выбором, а
|
||||||
|
требованием PocketBase: `go mod tidy` поднял директиву сам, а образ подтянули
|
||||||
|
следом. То есть сегодняшнее число никем не назначено, и повторится это на
|
||||||
|
следующей же зависимости.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- директива `go` в `go.mod`;
|
||||||
|
- сборочный слой `Dockerfile`;
|
||||||
|
- строка стека в `CLAUDE.md` и в `README.md`.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Модуль, образ и документы называют одну версию Go. Оракул — `grep` по
|
||||||
|
четырём местам: `go.mod`, `Dockerfile`, `CLAUDE.md`, `README.md`; все дают
|
||||||
|
одно число.
|
||||||
|
- Сборка на объявленной версии проходит. Оракул — `task image` и
|
||||||
|
`CGO_ENABLED=0 go build ./...` на чистом дереве.
|
||||||
|
- Гейт зелёный целиком. Оракул — `task gate`.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Версию выбирает человек: обновление до неподдерживаемой ещё зависимостями версии
|
||||||
|
ломает сборку молча. Выкладку не запускаем.
|
||||||
@@ -19,6 +19,13 @@
|
|||||||
и заливка в Object Storage, — и сколько времени и места на диске занимает
|
и заливка в Object Storage, — и сколько времени и места на диске занимает
|
||||||
шестичасовая запись на каждом из них.
|
шестичасовая запись на каждом из них.
|
||||||
|
|
||||||
|
**Звено, добавленное ревью 2026-08-12:** приём пишет тело записи на диск
|
||||||
|
дважды — разбор формы записывает во временный каталог всё, что не поместилось в
|
||||||
|
память, выделенную под multipart, а следом рабочая копия переливает то же
|
||||||
|
содержимое в свой файл. Верхняя
|
||||||
|
оценка на один запрос — два объявленных потолка записи (`entity.MaxRecordSize`,
|
||||||
|
8 ГиБ). Числа нет: самая длинная проверенная запись — 9,6 МБ.
|
||||||
|
|
||||||
## Куда ляжет ответ
|
## Куда ляжет ответ
|
||||||
|
|
||||||
`docs/research/intake-limits.md` — числами и с командой замера по каждому звену.
|
`docs/research/intake-limits.md` — числами и с командой замера по каждому звену.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Запланировано
|
- **Секция:** Запланировано
|
||||||
- **Зачем:** У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки.
|
- **Зачем:** У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Приложение узнаёт, кто к нему пришёл, и показывает каждому только его записи.
|
Приложение узнаёт, кто к нему пришёл, и показывает каждому только его записи.
|
||||||
Учётные записи заводит и проверяет внешний провайдер — Authelia по OIDC; своей
|
Учётные записи заводит и проверяет внешний провайдер — Authelia по OIDC; своей
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# 🔬 Квота по общему размеру загруженного на пользователя
|
||||||
|
|
||||||
|
- **Тип:** research
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
||||||
|
|
||||||
|
Заметка звучит так: «Квота по размеру загруженных файлов на пользователя. В
|
||||||
|
конфиге устанавливать квоту по общему размеру, по дефолту не больше 5GB».
|
||||||
|
|
||||||
|
**Взять в работу её нельзя, пока не решён спор с паспортом.**
|
||||||
|
`docs/passport.md`, раздел «Что целью не является», говорит прямо: «Цен, счетов
|
||||||
|
и отказов по исчерпании квоты не делаем: пользователя, потратившего слишком
|
||||||
|
много, останавливает разговор или отзыв доступа в Authelia». То же повторяет
|
||||||
|
`docs/security.md`, «Что вне модели»: квот нет и не будет.
|
||||||
|
|
||||||
|
Владелец 2026-08-12 решил не двигать границу сейчас, а разобрать позже. Разбор
|
||||||
|
отвечает на три вещи: двигается ли граница паспорта; отказывает ли сервис
|
||||||
|
пользователю или только оповещает владельца; на чём считается объём, раз учёта
|
||||||
|
по пользователям сегодня нет вовсе (задача `usage-accounting`).
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Пока раздел «Вопрос» пуст, запись числится сырьём и в работу не берётся. Правка
|
||||||
|
паспорта — решение человека: это граница домена, а не деталь.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Запланировано
|
- **Секция:** Запланировано
|
||||||
- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
|
- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Задача уходит в работу — человек закрывает приложение и получает сообщение,
|
Задача уходит в работу — человек закрывает приложение и получает сообщение,
|
||||||
когда текст готов. Пользователь Telegram это уже имеет: бот отвечает сам.
|
когда текст готов. Пользователь Telegram это уже имеет: бот отвечает сам.
|
||||||
|
|||||||
@@ -30,6 +30,14 @@
|
|||||||
экрана. Оракул — тест: подставной API отвечает `transcribe`, затем `done`;
|
экрана. Оракул — тест: подставной API отвечает `transcribe`, затем `done`;
|
||||||
экран переходит к тексту сам.
|
экран переходит к тексту сам.
|
||||||
|
|
||||||
|
**Учесть от ревью 2026-08-12:** имя файла в хранилище несёт хвост имени, данного
|
||||||
|
отправителем, — расширение берётся из него дословно, и запись
|
||||||
|
`разговор.тайное-слово` ложится на диск именем с этим хвостом. Сегодня наружу
|
||||||
|
оно не выходит: изъятие из инварианта приватности кончается журналом владельца.
|
||||||
|
Экран списка это меняет — имя попадает в ссылку на скачивание и в заголовок
|
||||||
|
ответа, то есть на страницу. Выхода за каталог хранения при этом нет, проверено
|
||||||
|
пятнадцатью враждебными именами.
|
||||||
|
|
||||||
## Рамки
|
## Рамки
|
||||||
|
|
||||||
Поиска по тексту, переименования и удаления записей не делаем. Правки текста не
|
Поиска по тексту, переименования и удаления записей не делаем. Правки текста не
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Сопровождение
|
- **Секция:** Сопровождение
|
||||||
- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
|
- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Наблюдаем **мы**, а не пользователь сервиса, — поэтому цель стоит в
|
Наблюдаем **мы**, а не пользователь сервиса, — поэтому цель стоит в
|
||||||
сопровождении, а не среди возможностей приложения. Пользовательская половина
|
сопровождении, а не среди возможностей приложения. Пользовательская половина
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 🔬 Уведомление SpeechKit о готовности вместо опроса
|
||||||
|
|
||||||
|
- **Тип:** research
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Шаг проверки дёргает операцию раз в 5 секунд всё время распознавания: часовая запись даёт порядка 720 обращений к платному сервису вместо одного ответа.
|
||||||
|
|
||||||
|
Заметка владельца от 2026-08-12. Сегодня готовность узнаётся только опросом:
|
||||||
|
воркер проверки берёт задачу, спрашивает операцию и ставит задержку в 5 секунд —
|
||||||
|
и так до конца распознавания. Числа задержек стоят в `docs/database.md`,
|
||||||
|
«Настройки с числовым значением».
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
|
||||||
|
Умеет ли отложенное распознавание SpeechKit сообщать о готовности само —
|
||||||
|
вызовом на наш адрес, очередью или иным способом, — и что это меняет для
|
||||||
|
сервиса: остаётся ли воркер проверки, чем закрывается пропущенное уведомление и
|
||||||
|
какой вход появляется снаружи.
|
||||||
|
|
||||||
|
## Куда ляжет ответ
|
||||||
|
|
||||||
|
- Наблюдение с командой и условиями — записка в `docs/research/`.
|
||||||
|
- Появляется вход снаружи — строка в `docs/security.md`, раздел «Недоверенный
|
||||||
|
вход», и в `docs/architecture.md`, «Внешние границы и форматы».
|
||||||
|
- Выбранный способ — решением в `docs/adr/`, потому что опрос он заменяет
|
||||||
|
целиком; реализация — отдельной задачей.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Прогон на реальных ключах Yandex запрещён — ответ ищется в документации сервиса
|
||||||
|
и, если понадобится замер, отдельным разрешением человека с названной ценой.
|
||||||
|
Уведомление требует адреса, доступного снаружи: сегодня он есть, но закрывать
|
||||||
|
его будет `oidc-login`, и это учитывается в ответе.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 🔬 Адрес объекта в тексте отказа SpeechKit
|
||||||
|
|
||||||
|
- **Тип:** research
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Текст отказа операции приходит от Yandex и уезжает в журнал и в колонку error_text: если он несёт URI объекта, из журнала снова собирается ссылка на чужую запись.
|
||||||
|
|
||||||
|
Гипотеза ревью задачи `pocketbase-storage` 2026-08-12, оставшаяся без оракула:
|
||||||
|
поднять настоящий отказ Yandex нечем, а прогон на реальных ключах запрещён.
|
||||||
|
Собственные цепочки отказов задача оборвала — чтение из хранилища и выгрузка в
|
||||||
|
Object Storage больше не называют ключ, — но текст, пришедший **чужими руками**,
|
||||||
|
идёт в журнал и в задачу как есть.
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
|
||||||
|
Несёт ли текст отказа операции распознавания адрес объекта, который мы отдали
|
||||||
|
SpeechKit, и в каких случаях? Если несёт — чем его отсечь, не потеряв причину
|
||||||
|
отказа: разбором по образцу, перечнем известных кодов отказа или отказом писать
|
||||||
|
чужой текст вовсе.
|
||||||
|
|
||||||
|
## Куда ляжет ответ
|
||||||
|
|
||||||
|
- Наблюдение с командой и условиями — записка в `docs/research/`, рядом с
|
||||||
|
`pocketbase-defaults.md`.
|
||||||
|
- Если адрес приходит — норма в спеку `storage`, требование «Файл отдаётся
|
||||||
|
ссылкой», и строка в `docs/security.md`, раздел «Из чего строятся пути и
|
||||||
|
ключи»; сама правка — отдельной задачей.
|
||||||
|
- Если не приходит — строка в ту же записку, чтобы гипотезу не поднимали заново.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Реальные ключи Yandex и боевые данные не трогаем. Отказ придётся вызывать
|
||||||
|
дешёвым способом: битый объект, объект вне бакета, отозванный доступ — что из
|
||||||
|
этого доступно, решается по ходу. Цена прогона в деньгах называется до запуска.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 🧹 Разобрать мелочи слоя хранилища
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Три мелочи ниже потолка триажа: цикл воркера пишет потерю захвата уровнем ERROR и считает её отказом, тип ошибки заведён там, где конвенция просит sentinel, а FileName несёт два разных смысла.
|
||||||
|
|
||||||
|
Все три пришли ревью задачи `pocketbase-storage` 2026-08-12 и остались ниже
|
||||||
|
потолка отчёта — то есть реальны, но не блокировали. Собраны одной задачей,
|
||||||
|
потому что живут в одном пакете и правятся одним заходом.
|
||||||
|
|
||||||
|
1. **Потеря захвата — не отказ шага.** Значение «задачу за время работы забрал
|
||||||
|
другой» доезжает до цикла воркера, который пишет его уровнем `ERROR` и
|
||||||
|
увеличивает счётчик отказов. `docs/conventions/logging.md` относит «задача
|
||||||
|
досталась повторно» к `WARN`, а метрика отказов после этого считает не отказы.
|
||||||
|
2. **Тип ошибки против sentinel.** `docs/conventions/errors.md` просит sentinel
|
||||||
|
там, где данных у ошибки не читает никто; у значения о потерянном захвате
|
||||||
|
вызывающий читает только сам факт.
|
||||||
|
3. **Одно поле в двух смыслах.** У местной копии `entity.File.FileName` — имя в
|
||||||
|
хранилище и последняя часть ссылки на скачивание, у внешней — ключ объекта.
|
||||||
|
Различает их только соседнее поле, а совпадают они сегодня дословно: на этом
|
||||||
|
совпадении держится верность строки `docs/architecture.md` про ключ объекта.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/contract/error.go` — значение о потерянном захвате;
|
||||||
|
- цикл воркера `internal/controller/worker` — уровень записи и счётчик отказов;
|
||||||
|
- `internal/entity/file.go` и его потребители в `internal/service`;
|
||||||
|
- строка про ключ объекта в `docs/architecture.md`, «Внешние границы и форматы».
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Потеря захвата не считается отказом и не пишется на уровне владельца сервиса.
|
||||||
|
Оракул — тест воркера: после прогона с потерянным захватом счётчик отказов не
|
||||||
|
вырос, а строка в журнале идёт уровнем `WARN`.
|
||||||
|
- Форма значения об отказе совпадает с конвенцией. Оракул — `golangci-lint run`
|
||||||
|
плюс чтение `docs/conventions/errors.md` рядом с кодом.
|
||||||
|
- Два смысла имени файла разведены либо названы в документе. Оракул — `grep` по
|
||||||
|
потребителям поля: каждый читает его в одном смысле, а `docs/architecture.md`
|
||||||
|
говорит про ключ объекта то же, что делает код.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Раскладку файлов на диске и имена коллекций не трогаем: формат хранения объявлен
|
||||||
|
необратимым, а шаг схемы, уехавший на сервер, не переписывается.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Направления
|
- **Секция:** Направления
|
||||||
- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
|
- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Текст записи выдаётся уровнями: сырая расшифровка, вычитанный литературный
|
Текст записи выдаётся уровнями: сырая расшифровка, вычитанный литературный
|
||||||
текст, заголовок в одну строку, темы-теги и пересказ в два-три предложения.
|
текст, заголовок в одну строку, темы-теги и пересказ в два-три предложения.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Запланировано
|
- **Секция:** Запланировано
|
||||||
- **Зачем:** Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги.
|
- **Зачем:** Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Человек отдаёт диктофонную запись или видео из семейного архива с телефона, по
|
Человек отдаёт диктофонную запись или видео из семейного архива с телефона, по
|
||||||
сотовой сети, и загрузка либо доходит, либо честно говорит, что не дошла.
|
сотовой сети, и загрузка либо доходит, либо честно говорит, что не дошла.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Сопровождение
|
- **Секция:** Сопровождение
|
||||||
- **Зачем:** Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется.
|
- **Зачем:** Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Владелец открывает страницу и видит по каждому пользователю объём загруженного,
|
Владелец открывает страницу и видит по каждому пользователю объём загруженного,
|
||||||
длительность записей в минутах и расход на внешние сервисы. Приглашая человека,
|
длительность записей в минутах и расход на внешние сервисы. Приглашая человека,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Запланировано
|
- **Секция:** Запланировано
|
||||||
- **Зачем:** Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем.
|
- **Зачем:** Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
У каждого своя мера: одному нужна только сырая расшифровка, другому — все пять
|
У каждого своя мера: одному нужна только сырая расшифровка, другому — все пять
|
||||||
уровней текста. Настройки принадлежат человеку, а не общему конфигу сервиса, и
|
уровней текста. Настройки принадлежат человеку, а не общему конфигу сервиса, и
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** goal
|
- **Тип:** goal
|
||||||
- **Секция:** Запланировано
|
- **Секция:** Запланировано
|
||||||
- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
|
- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
|
||||||
|
- **Теги:** decomposed
|
||||||
|
|
||||||
Приложение получает поверхность, на которой запись загружают и забирают текст,
|
Приложение получает поверхность, на которой запись загружают и забирают текст,
|
||||||
не открывая Telegram и не вызывая API руками. Ставится оно на телефон и
|
не открывая Telegram и не вызывая API руками. Ставится оно на телефон и
|
||||||
|
|||||||
Reference in New Issue
Block a user