tasks: очередь расставлена от базы к деталям

- порядок беклога и роадмапа назначен слоями: проверки, которым можно
  верить → долги входа → владелец записи и контракт API → конвейер под
  тестами → приложение и возможности поверх; у каждого движения записана
  причина;
- заведены восемь задач под пункты «Завершения», которых не закрывала ни
  одна запись, — цель any-audio-source была без задач вовсе;
- у четырёх задач сняты критерии, требовавшие того, что делает задача ниже
  по очереди; исправлены ссылки на несуществующий repo/sqlite и на
  отменённую разведку об очереди.
This commit is contained in:
av
2026-08-12 20:48:53 +03:00
parent ddc34b3182
commit 8739b18a9f
73 changed files with 490 additions and 139 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Сделать страницу статистики для владельца
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Страница показывает собранный учёт: без учёта показывать нечего.
- **Зачем:** Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет.
- **Теги:** goal:usage-stats
+2 -1
View File
@@ -1,8 +1,9 @@
# 🎯 Принимается запись любого формата, включая дорожку из видео
- **Тип:** goal
- **Секция:** Направления
- **Секция:** Направления — Перечень форматов не замерен, и потолок длины у видео тот же, что у долгих записей: тянется следом за ними.
- **Зачем:** Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил.
- **Теги:** decomposed
Человек отдаёт файл, не думая о том, что внутри: аудио любого распространённого
контейнера или видео, из которого нужна только речь. Подготовка на стороне
+4 -3
View File
@@ -1,7 +1,7 @@
# ✨ Пускать скрипты в API по личным токенам
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Второй способ представиться ставится на готовые владельца и контракт, иначе форма ошибки переписывается дважды.
- **Зачем:** Вход через OIDC закрывает API целиком, а скрипту браузерная сессия недоступна: автоматизировать загрузку станет нечем.
- **Теги:** goal:multi-user
@@ -18,7 +18,6 @@
- таблица токенов: владелец, имя, отпечаток, время выпуска и последнего
обращения, и её миграция;
- эндпоинты выпуска, перечня и отзыва токена;
- экран настроек — место, где токен выпускают и отзывают;
- `docs/security.md` — второй способ представиться и хранение отпечатка;
- `README.md` — пример вызова API скриптом.
@@ -38,4 +37,6 @@
Учётные записи по-прежнему заводит Authelia — свою регистрацию не делаем.
Сроков жизни и областей действия у токена не заводим: он даёт права владельца
целиком. Берётся после `oidc-login`: до неё представляться некому.
целиком. Берётся после `oidc-login`: до неё представляться некому. Экрана выпуска здесь
нет — приложения ещё не существует, токен выпускается запросом к API; место
токена на экране настроек заводит `settings-screen`.
@@ -0,0 +1,32 @@
# 🔬 Перечень форматов, которые конвейер принимает на самом деле
- **Тип:** research
- **Категория:** Очередь — Форматы: сначала замер того, что конвейер берёт на самом деле.
- **Зачем:** Команда ffmpeg проверена на голосовых Telegram, а что она берёт помимо них, не мерил никто: перечень выведен из документации, а не из прогона.
- **Теги:** goal:any-audio-source
Двигает пункты 1 и 4 «Завершения» цели: перечень принимаемых форматов замерен и
записан, а расхождение `ogg/vorbis` против заявленного SpeechKit `OGG_OPUS`
разобрано.
## Вопрос
Какие форматы доходят до текста целиком, какие ломаются на конвертации, какие —
на распознавании, и чем на самом деле кодирует конвертер: `vorbis` или `opus`.
## Куда ляжет ответ
- `docs/research/audio-formats.md` — таблица «формат на входе → исход», с
командой замера и версией ffmpeg, на которой он сделан;
- расхождение `ogg/vorbis` против `OGG_OPUS`: строка о том, устранено оно или
проверенно безвредно, и чем это подтверждено;
- форматы, которые принять нельзя, — задачей об отказе на приёме, с провенансом
этой разведки.
## Рамки
Замер идёт на своих файлах во временном каталоге и на подставном распознавателе
`internal/adapter/recognizer/memory.go`; прогон на реальных ключах Yandex
запрещён — там, где без настоящего SpeechKit не обойтись, ответ берётся из
задачи `speechkit-limits`, а не оплачивается заново. Видеофайлы здесь только
измеряются, приём их заводит `video-audio-track-intake`.
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Загрузка большого файла частями
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Разведка про докачку идёт после того, как обычная загрузка доведена: иначе сравнивать не с чем.
- **Зачем:** Гигабайтный файл едет одним запросом, и обрыв на девяноста процентах начинает его заново.
Шестичасовая диктофонная запись и видео из семейного архива весят гигабайты, а
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Переименовать образец конфига в config.example.toml
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Поднято наверх: шесть задач ниже правят конфиг и каждая допишет старое имя образца, удлиняя перечень мест переименования.
- **Зачем:** Конвенция называет config.dist.toml объявленным расхождением, но тут же пишет это имя как правило — документ противоречит сам себе, а образец расходится с конвенцией.
Конвенция конфигурации взята из проекта jellybit и **сама называет сегодняшнее
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Прервать шаг конвейера отменой контекста
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Отмена контекста трогает те же шаги, что и таймауты: одна окрестность кода, два захода дороже одного.
- **Зачем:** Воркер читает ctx только между итерациями: остановка контейнера ждёт конца шага, а на занятом писателе один запрос к хранилищу держится до 9,5 секунды при мягком таймауте в 5.
Долг назван в `docs/architecture.md` — раздел про capability `pipeline`, где
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Человек убирает свою запись из архива вместе со всеми текстами
- **Тип:** goal
- **Секция:** Направления
- **Секция:** Запланировано — Очередь у цели появилась: удаление записи стоит 32-й строкой и трогает конвейер, файлы и колонку дедупликации, которые к тому месту готовы.
- **Зачем:** Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда.
- **Теги:** decomposed
+4 -2
View File
@@ -1,7 +1,7 @@
# ✨ Узнавать уже загруженный файл по хеш-сумме
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Дедупликация ищет совпадение в пределах пользователя — то есть после владельца записи, и экономит деньги с первого дня приложения.
- **Зачем:** Один и тот же файл, отправленный дважды, распознаётся дважды и оплачивается дважды: приём не смотрит на содержимое вовсе.
- **Теги:** goal:upload-reliability
@@ -38,4 +38,6 @@
Владелец записи приходит из `record-ownership` — до неё дедупликация опирается
на того владельца, который уже есть. Хеш-сумма считается на сервере: подсчёт на
стороне приложения относится к `chunked-upload-choice`. Колонка добавляется во
всех четырёх местах репозитория SQLite (инвариант `CLAUDE.md`).
всех четырёх местах пакета хранилища `internal/adapter/repo/pocketbase`
`applyToRecord`, `recordToJob`, `acquireColumns`, `acquiredRow` с её `toJob`, —
плюс шаг схемы (инвариант `CLAUDE.md`).
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Удалять запись со всеми уровнями текста по требованию владельца
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Удаление трогает конвейер, файлы, объект хранилища и колонку дедупликации — всё это к этому месту уже готово.
- **Зачем:** Ни файлы, ни расшифровки не удаляются вовсе: убрать запись сегодня можно только руками в базе и в каталоге на сервере.
- **Теги:** goal:data-ownership
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Слать готовый текст на почту из учётной записи
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Второй канал на той же доставке.
- **Зачем:** Адрес почты приходит вместе с входом через OIDC, но почтового отправителя в сервисе нет.
- **Теги:** goal:ready-notification
+5 -5
View File
@@ -1,7 +1,7 @@
# 🧹 Задать таймауты обращениям к внешним сервисам
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Молчащий внешний сервис держит шаг до истечения часового захвата — правится под свежими тестами конвейера.
- **Зачем:** Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
Все обращения наружу идут с `context.Background()` либо через `http.Get` без
@@ -33,9 +33,6 @@
- У каждого обращения наружу есть таймаут, и его значение задаётся конфигом.
Оракул — тест на подставном сервере, который не отвечает: вызов
возвращается с ошибкой за назначенное время, а не висит.
- Отмена контекста при остановке приложения прерывает шаг конвейера. Оракул —
тест: отменённый контекст возвращает управление из шага, задача остаётся в
прежнем состоянии.
- Прерванная по таймауту задача достаётся повторно и доходит до текста. Оракул
— тест на повторный прогон шага после отказа по таймауту.
- Числа таймаутов записаны в `docs/database.md`. Оракул — `task gate`.
@@ -43,4 +40,7 @@
## Рамки
Повторов с нарастающей паузой не заводим — это отдельная работа; здесь только
таймаут и отмена.
таймаут. Прерывание шага отменённым контекстом сюда не входит: методы записи
хранилища контекста не принимают вовсе, и этим занимается следующая задача
`context-cancel-in-pipeline`. Контекст первым аргументом интерфейсов эта задача
всё же протаскивает — без него таймаут некуда передать.
+41
View File
@@ -0,0 +1,41 @@
# ✨ Считать вызовы, отказы и длительность по каждому внешнему сервису
- **Тип:** feature
- **Категория:** Очередь — Метрики внешних сервисов пишутся в выбранном словаре, а не переписываются потом.
- **Зачем:** Ни у Telegram, ни у Object Storage, ни у SpeechKit нет ни одной метрики: отказ внешнего сервиса виден только строкой в журнале контейнера.
- **Теги:** goal:service-observability
Двигает пункты 2 и 5 «Завершения» цели: у каждого внешнего сервиса появляются
вызовы, отказы и длительность, а расход на платные сервисы виден числом.
Внешних сервисов сегодня четыре — Telegram, Object Storage, SpeechKit и
`ffmpeg`/`ffprobe` как внешний процесс; пятым станет языковая модель. Метрика
заводится одной формой на все, чтобы шестой сервис не приносил шестого способа
считать.
## Затрагивает
- `internal/metrics` — форма метрики внешнего вызова: имя сервиса, операция,
исход, гистограмма длительности;
- адаптеры `internal/adapter/*` — место, где вызов оборачивается замером;
- `docs/architecture.md`, раздел эксплуатации — перечень метрик;
- `docs/conventions/` — правило: новый внешний вызов приходит со своей меткой,
а не со своей метрикой.
## Критерии приёмки
- У каждого из четырёх внешних сервисов есть счётчик вызовов, счётчик отказов и
гистограмма длительности. Оракул — тест: прогнать по одному вызову каждого
адаптера с подставным собеседником и снять `/metrics`.
- Отказ внешнего сервиса виден отдельно от успеха и не теряется в общем счётчике.
Оракул — тест: подставной собеседник отвечает отказом, метка исхода в метрике
отличается.
- Минуты распознавания и объём заливки видны числом. Оракул — тест: после шага
распознавания счётчик минут вырос на длительность записи, а после заливки в
Object Storage счётчик объёма — на размер файла.
## Рамки
Трассировку здесь не заводим — чем развивать наблюдаемость, решает разведка
`opentelemetry-fit`; эта задача остаётся в том словаре, который она выберет.
Учёт по пользователям — задача `usage-accounting`, здесь метрики сервисные.
@@ -1,7 +1,7 @@
# 🐞 Починить путь миграций в настройке сверки документов
- **Тип:** fix
- **Категория:** Очередь
- **Категория:** Очередь — Шаг гейта зелен при изменённой миграции и нетронутом database.md: на этом шаге стоит приёмка всякой следующей задачи, трогающей схему.
- **Зачем:** Ключ migrations указывает на каталог migrations/, которого в репозитории нет: шаг гейта зелен при изменённой миграции и нетронутом database.md, а конвенции числят этот род механизированным.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+14 -13
View File
@@ -1,9 +1,8 @@
# 🧹 Считать покрытие изменённых строк шагом гейта
# 🧹 Ронять гейт на изменённой функции, которую не выполняет ни один тест
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Непокрытую изменённую функцию дважды ловил проход ревью, а не машина; порог решён 2026-08-12, брать можно.
- **Зачем:** Свойство «изменённое место покрыто хоть одним тестом» записано в docs/review.md, но не механизировано: за две задачи подряд непокрытые шаги ловили руками.
- **Теги:** question
`CLAUDE.md`, раздел «Гейт», объявляет прямо: «покрытие изменённых строк не
считается ничем». Цена этого измерена дважды. В задаче
@@ -11,6 +10,13 @@
`pocketbase-storage` два из трёх шагов конвейера переписали целиком и не
выполнили ни одним тестом — нашёл это проход ревью, а не машина.
**Единица счёта — функция, а не строка** (решение владельца 2026-08-12). Шаг
краснеет на новой или изменённой функции, которую не выполняет ни один тест;
доля покрытых строк внутри неё не считается и порогом не ограничивается. Довод:
процент изменённых строк роняет гейт на всякой ветке отказа, которую нечем
изобразить в тесте, а порог ниже ста пришлось бы брать из ниоткуда. Именно
непокрытая целиком функция — то, что дважды ловили руками.
## Затрагивает
- набор шагов `task gate` в `Taskfile.yml` и переменная `BASE` как база диффа;
@@ -25,16 +31,11 @@
адресом.
- Изменение, не трогающее код, шаг не гоняет. Оракул — `task gate` на дереве с
правкой одной только документации: шаг сообщает о пропуске с причиной.
- Порог назван числом в `CLAUDE.md` и совпадает с тем, что проверяет шаг. Оракул
`task gate`, шаг `docs.py check`.
## Вопросы
- **Какой порог и что считать изменённой строкой.** Варианты: сто процентов
изменённых строк (роняет на всякой ветке отказа, которую нечем изобразить),
порог ниже ста (число берётся из ниоткуда), только новые функции целиком.
Цена решения — частота ложных отказов гейта; пока решения нет, шаг заводить
нельзя.
- Единица счёта названа в `CLAUDE.md` и совпадает с тем, что проверяет шаг.
Оракул`task gate`, шаг `docs.py check`.
- Функция, тронутая правкой на одну строку, шаг не роняет, если её вызывает хоть
один тест. Оракул — прогон на дереве с однострочной правкой внутри покрытой
функции: шаг зелёный.
## Рамки
@@ -1,7 +1,7 @@
# 🧹 Ловить уязвимости в зависимостях шагом гейта
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — govulncheck находит две достижимые уязвимости, а гейт о нём не знает: о третьей узнать будет неоткуда.
- **Зачем:** govulncheck находит две достижимые уязвимости в клиентах Yandex, а ни гейт, ни список «чего в гейте нет» о нём не знают: узнать о третьей будет неоткуда.
Обе найдены ревью задачи `pocketbase-storage` 2026-08-12 и достижимы из кода
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Завести недостающие проверки: shellcheck, hadolint и тест скрипта сверки версий
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Инструмент: shellcheck и hadolint не ловят ничего из перечисленного выше и потому ждут.
- **Зачем:** Скрипты оболочки и Dockerfile машина не проверяет: shellcheck и hadolint не заведены, lefthook смотрит только gitleaks, а единственный исполняемый скрипт проекта на shell — scripts/check-go-version.sh — разбирали на ревью глазами построчно, потому что машине его показать нечем.
Собрана из двух источников: заметки владельца от 2026-08-12 про lefthook и
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Привести коды выхода шагов гейта к общему словарю
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Словарь кодов уже разъехался с обёртками: три новых шага гейта ниже напишутся по неверному образцу и попадут под переписывание.
- **Зачем:** CLAUDE.md объявляет словарь кодов общим для четырёх проверочных шагов и описывает код 3 как «каталог или файл не найден», но все четыре обёртки в Taskfile.yml при отсутствующем скрипте отдают 1 («дрейф»), из-за чего читатель кода идёт искать разъехавшееся там, где просто неполно дерево.
Расхождение старое и повторено всеми четырьмя обёртками одинаково — дефектом
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Разобрать мелочи http-транспорта
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Поднято до контракта API: маршруты объявлены дважды, и переименование пути проходит тесты зелёным — переписывать контроллер под слепыми проверками нельзя.
- **Зачем:** Маршруты зарегистрированы дважды, и переименование пути в main.go проходит проверки зелёным; обработчик пишет в журнал через стандартный log и дублирует запись, уже сделанную сервисом.
- **Теги:** review-2026-08-11
+40
View File
@@ -0,0 +1,40 @@
# ✨ Показывать заголовок в списке, отбирать список по темам и считать токены
- **Тип:** feature
- **Категория:** Очередь — Три пункта «Завершения» цели не закрывала ни одна задача: показывать и отбирать можно, когда заголовки и темы уже считаются.
- **Зачем:** Заголовок, темы и пересказ считаются, но список по-прежнему показывает первые слова расшифровки и не отбирается ничем, а расход на модель не виден числом.
- **Теги:** goal:text-insights
Двигает пункты 1, 4 и 6 «Завершения» цели — те три, где выводы из текста
становятся видны человеку и владельцу: заголовок в списке (1), отбор по темам
(4), стоимость числом (6). Сами уровни считает `llm-insights-adapter`, показать
их некому: экран списка написан раньше и знает только первые слова расшифровки.
Берётся после `llm-insights-adapter`: пока заголовков и тем нет, показывать и
отбирать нечего.
## Затрагивает
- контракт списка записей: заголовок в элементе и отбор по теме параметром
запроса;
- экран списка — вместо первых слов расшифровки заголовок, и отбор по теме;
- индекс по темам в хранилище, если отбор без него читает всю выборку;
- `internal/metrics` — число обработанных записей и число токенов языковой
модели;
- `docs/architecture.md`, раздел эксплуатации — что означает счётчик токенов.
## Критерии приёмки
- В списке у записи с заголовком показан заголовок, а не первые слова
расшифровки; у записи без заголовка — прежнее поведение. Оракул — тест экрана
на подставном API с обеими записями.
- Список отбирается по теме и отдаёт только записи с ней. Оракул — тест API:
выборка по теме из трёх записей возвращает одну.
- Расход языковой модели виден числом: сколько записей обработано и сколько
потрачено токенов. Оракул — тест шага на подставной модели: оба счётчика
выросли на значения из ответа.
## Рамки
Поиска по тексту расшифровки не делаем — отбор идёт только по темам. Разбивку
расхода по пользователям считает `usage-accounting`, здесь метрика общая.
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Сделать приложение устанавливаемым на телефон
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Ставить на телефон есть смысл, когда есть что ставить.
- **Зачем:** Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет.
- **Теги:** goal:web-access
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Потолки приёма, конвертации и заливки по длине записи
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Второй замер — остальные четыре звена.
- **Зачем:** Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно.
- **Теги:** goal:long-recordings
+41
View File
@@ -0,0 +1,41 @@
# ✨ Собирать путь одной записи по конвейеру запросом
- **Тип:** feature
- **Категория:** Очередь — Пункт 3 «Завершения» цели не закрывала ни одна задача; применяет словарь, который выберет разведка строкой выше.
- **Зачем:** Звенья пути связаны только идентификатором задачи в строках журнала: чтобы понять, где запись провела минуты, владелец читает логи контейнера глазами.
- **Теги:** goal:service-observability
Двигает пункт 3 «Завершения» цели: путь одной записи по конвейеру собирается
запросом, а не чтением логов глазами.
Путь длиной в минуты идёт через четыре внешних сервиса и три воркера. Сегодня
его звенья связывает `job_id` в строках журнала, и собирает их человек.
**Форма ответа зависит от исхода разведки** `opentelemetry-fit`: с OpenTelemetry
это трасса со спанами на шаг, без него — отметки времени переходов у самой
задачи и запрос по ним. Разведка выбирает словарь, эта задача его применяет.
## Затрагивает
- таблица задач: отметки времени перехода между состояниями и их шаг схемы —
либо экспорт спанов, если разведка выберет трассировку;
- шаги конвейера — место, где отмечается начало и конец звена;
- способ прочитать собранное: запрос к хранилищу либо внешний приёмник трасс;
- `docs/architecture.md`, раздел эксплуатации — как читается путь записи;
- `docs/security.md` — путь записи не несёт ни текста, ни имени файла.
## Критерии приёмки
- Путь записи от приёма до готового текста читается одним обращением, без чтения
журнала. Оракул — тест: прогнать задачу через все шаги на подставных адаптерах
и получить длительность каждого звена.
- Путь отказавшей записи показывает звено, на котором она встала. Оракул — тест
с отказом на конвертации: звено названо, дальнейших звеньев нет.
- Собранное не несёт содержимого записи. Оракул — тест: в ответе только
идентификаторы, состояния, длительности и расширение.
## Рамки
Берётся после `opentelemetry-fit`: пока словарь не выбран, работа рискует быть
переписанной целиком. Хранение путей дольше срока жизни задачи за задачей не
стоит.
+14 -7
View File
@@ -1,7 +1,7 @@
# ✨ Свести приём и чтение записей к одному контракту для приложения
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Единая точка трансляции доменной ошибки — база и для экранов, и для токенов; список своих записей заводится после владельца, а не до.
- **Зачем:** Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем.
- **Теги:** goal:web-access
@@ -25,10 +25,10 @@
## Критерии приёмки
- Сбой базы при чтении задачи даёт `500`, а не `404`. Оракул — тест с
репозиторием, возвращающим ошибку драйвера.
- Негодный файл даёт `400` с человекочитаемым текстом, а не `500`. Оракул —
тест: файл, который отвергает разбор метаданных.
- Код ответа отвечает причине отказа, а не месту, где он случился: сбой базы при
чтении даёт `500`, а не `404`, негодный файл — `400` с человекочитаемым
текстом, а не `500`. Оракул — два теста: репозиторий, возвращающий ошибку
драйвера, и файл, который отвергает разбор метаданных.
- Тело ошибки одной формы на всех эндпоинтах, не содержит сырого `err.Error()` и
опечатки `transcibe`. Оракул — тест на четырёх ветвях отказа (форма совпадает,
текста внутренней ошибки в теле нет) плюс пустой `grep -rn 'transcibe'
@@ -37,12 +37,19 @@
тест на выборке больше страницы.
- Отображение ошибки живёт в одной функции, и она названа в
`docs/architecture.md`. Оракул — `task gate`, шаг `docs.py check`.
- Ответ приёма отдаёт список заведённых записей и место под признак повторного
файла, даже когда файл в запросе один. Оракул — тест приёма: тело ответа —
список из одного элемента, у элемента есть поле признака повтора.
## Рамки
Аутентификацию и владельца не заводим — это `oidc-login` и `record-ownership`;
задача про форму контракта. Публичный контракт после мерджа обратной правкой не
откатывается — поэтому сюда же приехала опечатка `transcibe` в тексте ошибки
приёма (нашёл проход `review-specs` на ревью change
откатывается, и по решению владельца от 2026-08-12 форма ответа приёма
согласуется здесь один раз — сразу списком и с местом под признак повтора, —
чтобы `dedup-by-content-hash`, `multi-file-upload` и `reject-oversized-recording`
её не переписывали, а экран загрузки не переделывался под вторую форму. Приём
по-прежнему берёт из запроса один файл: меняется форма ответа, не число файлов.
Сюда же приехала опечатка `transcibe` в тексте ошибки приёма (нашёл проход `review-specs` на ревью change
`2026-08-11-fix-http-handler-tests`): в одиночку текст ошибки менять нельзя,
а здесь контракт переписывается целиком.
+3 -4
View File
@@ -1,7 +1,7 @@
# ✨ Отдавать вычитанный текст рядом с сырым
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Вычитанный текст считается тем же адаптером.
- **Зачем:** Сырая расшифровка идёт без знаков препинания, с повторами и словами-паразитами: читать её подряд тяжело, а другого уровня текста нет.
- **Теги:** goal:text-insights
@@ -26,8 +26,6 @@
сырое совпадает с ответом распознавания.
- Отказ модели на вычитке не роняет задачу и не портит сырой текст. Оракул —
тест с моделью, возвращающей ошибку: задача в `done`, сырой текст на месте.
- Выключенный в настройках уровень не запрашивается и остаётся пустым. Оракул —
тест с выключенной вычиткой: обращений за ней нет.
- Экран показывает оба уровня с переключением между ними. Оракул — тест экрана
на подставном API.
@@ -35,4 +33,5 @@
Ручной правки текста человеком не делаем — это граница паспорта: сервис отдаёт
машинную вычитку, а редактором не становится. Берётся после
`llm-insights-adapter`: клиент модели заводит она.
`llm-insights-adapter`: клиент модели заводит она. Выключение уровня настройкой
пользователя сюда не входит — это `settings-applied-in-pipeline`.
+4 -6
View File
@@ -1,7 +1,7 @@
# ✨ Считать заголовок, темы и пересказ внешней моделью
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Уровни текста: сюда приходит пятая внешняя зависимость, и конвейер к этому моменту покрыт тестами.
- **Зачем:** Расшифровка доходит стеной текста: ни заголовка, ни тем, ни пересказа сервис не считает, и клиента языковой модели в нём нет.
- **Теги:** goal:text-insights
@@ -32,16 +32,14 @@ OpenAI-совместимым интерфейсом за шлюзом bifrost,
- Отказ и молчание модели не роняют задачу: текст доходит до человека без
выводов. Оракул — тест с моделью, возвращающей ошибку и таймаут: задача в
`done`, текст на месте, в журнале одна запись уровня `WARN`.
- Уровень, выключенный в настройках пользователя, у модели не запрашивается, а
настройка берётся та, что действовала на приёме записи. Оракул — тест с
выключенными темами и тест с настройкой, изменённой после приёма: вызовов за
темами нет в обоих случаях.
- Ключ и адрес шлюза не попадают ни в журнал, ни в ответ. Оракул — тест шага с
перехваченным журналом.
## Рамки
Литературный текст сюда не входит — это `literary-text-level`. Своей модели не
Литературный текст сюда не входит — это `literary-text-level`. Выборочность по
настройкам пользователя тоже: здесь считаются все уровни, а выключать их учит
`settings-applied-in-pipeline`. Своей модели не
держим и промптов в код не зашиваем сверх необходимого. Прогон на боевом ключе
ради проверки запрещён: подставной адаптер. Обращение к модели платное, и
объём расхода учитывает `usage-accounting`.
@@ -1,7 +1,7 @@
# 🧹 Поднимать сервис локально без действующего токена бота
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Поднято к долгам входа: живой прогон нужен именно им, а сегодня его нет ни у одной задачи.
- **Зачем:** Адаптер Telegram проверяет токен обращением к Telegram и роняет старт, а боевым токеном запускаться запрещено: проверить поведение живым прогоном не может ни одна задача.
Замечено при попытке проверить вход вживую в задаче `oidc-login` 2026-08-12;
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Четыре недоказанные гипотезы о поверхности входа
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Разведка закрывает тему входа последней: остальные три задачи меняют то, что она проверяет.
- **Зачем:** Ревью назвало четыре пути, которых не смогло ни подтвердить, ни опровергнуть: браузера и живого провайдера в прогоне не было.
Провенанс — отчёт триажа ревью задачи `oidc-login` 2026-08-12,
@@ -1,7 +1,7 @@
# 🧹 Строить адрес входа из настроек коллекции, а не из конфига
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Замыкает тройку правок обработчиков входа.
- **Зачем:** Первая половина входа собрана руками из конфига и на настройки провайдера не смотрит, вторая берётся из коллекции: обновление библиотеки изменит только вторую половину.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Резать длинную запись на фрагменты и продолжать с места остановки
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Резка на фрагменты — самая глубокая переделка конвейера, и она идёт по замеренным числам.
- **Зачем:** Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз.
- **Теги:** goal:long-recordings
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Запись длиной до шести часов доходит до текста
- **Тип:** goal
- **Секция:** Направления
- **Секция:** Направления — Цель начинается с двух замеров и кончается резкой на фрагменты — самой глубокой переделкой конвейера: очереди внутри нет, пока числа не получены.
- **Зачем:** Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, границы модели deferred-general неизвестны, а перезапуск на середине начинает распознавание заново.
- **Теги:** decomposed
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Отдавать текст в сотни килобайт файлом, а не сотней сообщений
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Сотни килобайт текста появляются только после долгих записей.
- **Зачем:** Отправитель Telegram режет текст по 4000 знаков: расшифровка шестичасовой записи придёт сотней сообщений подряд.
- **Теги:** goal:long-recordings
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Покрыть тестами разбор вывода ffprobe
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Разбор вывода ffprobe не проверяет ничто, а приём видео его расширяет.
- **Зачем:** Проверки приёма перестали звать настоящий ffprobe 2026-08-11, а своего теста у адаптера метаданных нет: разбор JSON и отличие «программы нет в PATH» от «обработка отказала» не проверяет ничто.
- **Теги:** review-2026-08-11
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Принимать до десяти файлов одной загрузкой
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Пачка файлов заводится на готовом экране загрузки и готовой дедупликации.
- **Зачем:** Приём берёт один файл в запросе, а с телефона выбирают пачку сразу: десять записей значат десять заходов на экран загрузки.
- **Теги:** goal:upload-reliability
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Сервисом пользуются несколько человек, и записи одного не видны другому
- **Тип:** goal
- **Секция:** Запланировано
- **Секция:** Запланировано — Владелец записи — фундамент, на котором стоят список своих записей, дедупликация, удаление, учёт расхода и квота: пока его нет, остальные цели строятся на песке.
- **Зачем:** У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки.
- **Теги:** decomposed
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Отправлять готовый текст через apprise и ntfy
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Канал уведомлений выбирается в настройках, которые уже есть.
- **Зачем:** Пользователь веба узнаёт о готовности только опросом с открытого экрана.
- **Теги:** goal:ready-notification
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Сопровождение: словарь метрик выбирается до того, как метрик станет втрое больше.
- **Зачем:** Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
- **Теги:** goal:service-observability
+1 -1
View File
@@ -1,7 +1,7 @@
# 🐞 Убирать записанный файл, когда приём отказал на середине
- **Тип:** fix
- **Категория:** Очередь
- **Категория:** Очередь — Осиротевший файл — тот же отказ на середине, и чинится тем же местом приёма.
- **Зачем:** Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
- **Теги:** review-2026-08-11, goal:upload-reliability
+38
View File
@@ -0,0 +1,38 @@
# ✨ Оповещать владельца об отказе, не дожидаясь жалобы
- **Тип:** feature
- **Категория:** Очередь — Правило оповещения ставится на метрику, которой до этого нет.
- **Зачем:** Об отказе владелец узнаёт от пользователя: правил оповещения нет ни на одной метрике, а метрики читают глазами.
- **Теги:** goal:service-observability
Двигает пункт 4 «Завершения» цели: владелец узнаёт об отказе сам.
Берётся после `external-service-metrics` и `stalled-pipeline-metric`: правило
оповещения ставится на метрику, а метрик, на которых его ставить, сегодня нет.
Доставку берём ту же, что и пользовательскую, — apprise с ntfy: второй канал
ради того же сообщения не заводим.
## Затрагивает
- правила оповещения: где они живут — в сервисе или в выкладке
`pet-project-server`, выбор входит в задачу;
- секция конфигурации канала оповещения владельца;
- `docs/architecture.md`, раздел эксплуатации — кто и о чём оповещает;
- `CLAUDE.md` — запрет на выкладку остаётся: правила выкладывает человек.
## Критерии приёмки
- Задача, ушедшая в `failed`, доходит до владельца сообщением. Оракул — тест с
подставным каналом: после перевода задачи в `failed` канал получил сообщение с
идентификатором задачи.
- Сообщение не несёт ни текста расшифровки, ни имени файла пользователя. Оракул
— тест: в отправленном теле только идентификаторы, длина и причина.
- Недоступность канала оповещения не роняет конвейер. Оракул — тест: канал
отвечает отказом, задача остаётся в прежнем состоянии, в журнале одна строка.
## Рамки
Выкладку правил запускает человек — `inv pl -- transcriber` из
`pet-project-server`. Пользовательские уведомления о готовности здесь не
трогаем: это цель `ready-notification`.
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Квота по общему размеру загруженного на пользователя
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Сырьё: спор с паспортом не решается, пока объём по пользователям не считается ничем.
- **Зачем:** Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
Заметка звучит так: «Квота по размеру загруженных файлов на пользователя. В
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Закрепить версию рантайм-базы образа
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Закрепление базы образа окупается при частых пересборках, а они начнутся с приложением.
- **Зачем:** Финальный слой Dockerfile собирается на alpine:latest, а task image идёт с --pull, поэтому два образа из одного коммита с разницей в неделю несут разный ffmpeg — регрессия конвертации после такой пересборки выглядит как задачи в failed при пустом диффе репозитория, и откат на прежний коммит её не чинит.
Класс тот же, ради которого задача `go-1-26-upgrade` завела шаг сверки версий:
+4 -5
View File
@@ -1,7 +1,7 @@
# 🧹 Покрыть тестами шаги конвейера и захват задачи
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Шаги конвейера не покрыты ничем, а удаление, уровни текста и резка длинных записей переписывают именно их: дальше без тестов идти вслепую.
- **Зачем:** Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем.
- **Теги:** review-2026-08-11
@@ -38,8 +38,7 @@ Telegram. Шаги конвейера — `FindAndRunConversionJob`, `FindAndRun
## Рамки
Задачу лучше брать **после** разведки
[job-queue-choice](job-queue-choice.md): она может сменить модель очереди
целиком, и тесты, написанные под сегодняшний захват, придётся выбросить вместе с
ним.
Модель очереди решена 2026-08-11 —
[очередь коллекцией PocketBase](../../docs/adr/ADR-2026-08-11-queue-as-pocketbase-collection.md),
— и разведки о ней больше нет. Тесты пишутся под сегодняшний захват.
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Проигрывать загруженную запись на экране записи
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Проигрывание живёт на экране записи, которого до списка нет.
- **Зачем:** Послушать загруженное приложение не даёт, а самой копии для этого у задачи нет: указатель на файл перезаписывается на каждом шаге конвейера и у готовой задачи ведёт на объект в Object Storage.
- **Теги:** goal:web-access
@@ -1,7 +1,7 @@
# 🐞 Убрать код провайдера из журнала запросов хранилища
- **Тип:** fix
- **Категория:** Очередь
- **Категория:** Очередь — Три задачи входа собраны подряд: все правят обработчики входа и одно требование спеки access — три захода в один файл дороже одного.
- **Зачем:** Строка запроса с кодом входа целиком уезжает в таблицу _logs и лежит там пять суток, хотя спека access требует, чтобы код в журнал не попадал.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым
- **Тип:** goal
- **Секция:** Запланировано
- **Секция:** Запланировано — Канал уведомлений выбирается в настройках, которые к этому месту уже есть.
- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
- **Теги:** decomposed
+8 -6
View File
@@ -1,7 +1,7 @@
# ✨ Привязать запись к владельцу и отдавать только свои
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Фундамент домена: без владельца знание UUID и есть право читать чужое, и на владельце стоят список, дедуп, удаление, учёт и квота.
- **Зачем:** У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
- **Теги:** goal:multi-user
@@ -14,7 +14,8 @@
- таблица задач и таблица файлов — колонка владельца и её миграция;
- `internal/contract`, интерфейсы репозиториев: чтение сужается владельцем;
- `internal/adapter/repo/sqlite` — все четыре запроса задач;
- `internal/adapter/repo/pocketbase` — все четыре места, где правятся колонки
очереди: `applyToRecord`, `recordToJob`, `acquireColumns` и `acquiredRow`;
- `internal/service`, оба метода заведения задачи;
- `GET /api/status/:id` — ответ на чужую запись;
- `docs/database.md` — схема и правило выборки.
@@ -24,9 +25,8 @@
- Запрос чужой записи по её идентификатору возвращает «не найдено», а не
содержимое и не «доступ запрещён». Оракул — тест: две сессии, задача первой
запрашивается второй, ответ 404 и пустое тело.
- Запись, заведённая из веба, принадлежит вошедшему; заведённая ботом —
пользователю, за которым закреплён чат. Оракул — тест на оба входа со сверкой
колонки владельца.
- Запись, заведённая из веба, принадлежит вошедшему. Оракул — тест приёма по
HTTP со сверкой колонки владельца.
- Выборка воркера владельцем **не** сужается: конвейер обрабатывает записи всех.
Оракул — тест: задачи двух владельцев проходят конвейер одним воркером.
- База заводится с чистого листа, колонка владельца обязательна и без умолчания.
@@ -35,4 +35,6 @@
## Рамки
Совместного доступа, ролей и передачи записи другому не делаем: владелец один и
неизменяем. Данные не переносим — чистый лист.
неизменяем. Данные не переносим — чистый лист. Владельца записи, пришедшей
ботом, здесь не назначаем: связи чата с учётной записью ещё нет, её заводит
`telegram-account-link` следующей задачей.
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Сделать экран списка своих записей и чтения текста
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Список и чтение текста берутся после экрана загрузки — так записано в самой задаче.
- **Зачем:** Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
- **Теги:** goal:web-access
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Отклонять на приёме запись сверх потолка
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Отклонять сверх потолка можно, когда потолок замерен.
- **Зачем:** Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста.
- **Теги:** goal:long-recordings
@@ -1,7 +1,7 @@
# 🧹 Судить ответ в тестах по готовому ответу
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Тест, читающий живую карту заголовков, зелен при неработающем коде: класс всплывал трижды и трижды стоил ложной приёмки.
- **Зачем:** Проверка, читающая изменяемую карту заголовков обработчика, зелена при неработающем коде: класс всплыл трижды, последний раз на уборке куки входа.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Настроить конвейер ревью по итогам прогона go-1-26-upgrade
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Настройка конвейера ревью по итогам прогона — работа над процессом, не над продуктом.
- **Зачем:** Прогон вскрыл две прорехи настройки: «Типовые узлы» знают только рантайм и не знают рода «проверочный шаг набора проверок», а «Триггеры метки» не видят оси «изменение трогает канон» — и именно она дала обе блокирующие находки.
Обе прорехи одного рода — настройка конвейера, живущая в `docs/review.md`, — и
@@ -1,7 +1,7 @@
# 🧹 Назвать в необратимом, что откат кода не откатывает шаг схемы
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Записать необратимость шага схемы надо до того, как схему тронут задачи фундамента.
- **Зачем:** Откат бинаря оставляет применённый шаг схемы в силе, и на этом строятся решения о выкладке: сегодня об этом не сказано нигде.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+1 -1
View File
@@ -1,7 +1,7 @@
# 🐞 Вести учёт употреблённых состояний входа на сервере
- **Тип:** fix
- **Категория:** Очередь
- **Категория:** Очередь — Тот же файл и та же спека, что у строки выше.
- **Зачем:** Одноразовость возврата держится на уборке куки, то есть на браузере: сервер не помнит, какие состояния уже потрачены.
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Состояние сервиса видно без чтения логов
- **Тип:** goal
- **Секция:** Сопровождение
- **Секция:** Сопровождение — Словарь метрик выбирается разведкой до того, как метрик станет втрое больше; наблюдаем мы, а не пользователь сервиса.
- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
- **Теги:** decomposed
@@ -0,0 +1,41 @@
# ✨ Считать только те уровни текста, что включены у владельца записи
- **Тип:** feature
- **Категория:** Очередь — Выключать нечего, пока уровней нет; настройка ставится сразу за ними, чтобы платная модель не считала лишнее.
- **Зачем:** Дом настроек есть, а конвейер их не читает: выключенный уровень всё равно уходит платной модели, и настройка ничего не экономит.
- **Теги:** goal:user-settings
Двигает пункты 1 и 4 «Завершения» цели: выключенный уровень не запрашивается у
языковой модели вовсе, а запись обрабатывается по той настройке, которая
действовала на приёме.
Берётся после `settings-screen` (настройки негде хранить) и после
`llm-insights-adapter` (нечего выключать).
Настройка снимается **на приёме** и едет с задачей: правка настроек посреди
обработки не меняет того, что уже считается, и не оставляет запись наполовину
обработанной.
## Затрагивает
- колонка снимка настроек у задачи и её шаг схемы;
- заведение задачи обоими входами: бот и приём по HTTP;
- шаг конвейера, обращающийся к языковой модели, — выбор уровней перед вызовом;
- `internal/contract` — интерфейс расчёта выводов: состав запрошенных уровней;
- `docs/database.md` — колонка снимка и её значение по умолчанию.
## Критерии приёмки
- Выключенный уровень не запрашивается у модели ни одним обращением. Оракул —
тест с подставным адаптером: у пользователя выключены пересказ и темы, адаптер
получает запрос без них и не вызывается второй раз.
- Запись обрабатывается по настройке, действовавшей на приёме. Оракул — тест:
завести задачу, сменить настройки владельца, прогнать шаг — считается прежний
состав уровней.
- У пользователя без настроек берутся значения по умолчанию, а задача не падает.
Оракул — тест на приёме от пользователя, у которого строки настроек нет.
## Рамки
Экран настроек и таблицу заводит `settings-screen` — здесь только чтение
настройки конвейером. Уже обработанные записи задним числом не пересчитываются.
+3 -2
View File
@@ -1,7 +1,7 @@
# ✨ Сделать экран настроек и хранить настройки по пользователю
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений.
- **Зачем:** Настроек у пользователя нет вовсе: уровни текста и канал уведомлений задаются общим конфигом сервиса.
- **Теги:** goal:user-settings
@@ -17,7 +17,8 @@
- таблица настроек пользователя: переключатели уровней текста, канал и адрес
уведомлений, и её миграция;
- эндпоинты чтения и записи своих настроек;
- новый экран приложения;
- новый экран приложения, и на нём же место выпуска и отзыва личного токена —
сам токен заводит `api-tokens`, экрана у него нет;
- значения по умолчанию для пользователя, у которого настроек ещё нет;
- `docs/database.md` — представление данных и значения по умолчанию.
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Собрать каркас приложения и раздать его из бинарника
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Каркас приложения: экранов нет и собирать их нечем, а на экране стоят настройки, удаление, уровни текста и статистика.
- **Зачем:** Экранов нет и собирать их нечем: ни сборки фронтенда, ни раздачи статики в проекте не существует.
- **Теги:** goal:web-access
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Уведомление SpeechKit о готовности вместо опроса
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Разведка про уведомление вместо опроса меняет тот же шаг проверки и видна метриками, заведёнными выше.
- **Зачем:** Шаг проверки дёргает операцию раз в 5 секунд всё время распознавания: часовая запись даёт порядка 720 обращений к платному сервису вместо одного ответа.
Заметка владельца от 2026-08-12. Сегодня готовность узнаётся только опросом:
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Адрес объекта в тексте отказа SpeechKit
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Тот же род утечки в журнал, но вне темы входа — идёт следом за ней.
- **Зачем:** Текст отказа операции приходит от Yandex и уезжает в журнал и в колонку error_text: если он несёт URI объекта, из журнала снова собирается ссылка на чужую запись.
Гипотеза ревью задачи `pocketbase-storage` 2026-08-12, оставшаяся без оракула:
+1 -1
View File
@@ -1,7 +1,7 @@
# 🔬 Потолки SpeechKit по длине записи и по формату
- **Тип:** research
- **Категория:** Очередь
- **Категория:** Очередь — Долгие записи начинаются с замера: без потолков ни отказ на приёме, ни резка не проектируются.
- **Зачем:** Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
- **Теги:** goal:long-recordings
+37
View File
@@ -0,0 +1,37 @@
# ✨ Показывать метрикой задачу, застрявшую в состоянии
- **Тип:** feature
- **Категория:** Очередь — Признак вставшего конвейера — вторая половина той же работы.
- **Зачем:** Вставший конвейер неотличим от простоя: возраст задачи в состоянии не считается, и очередь без движения выглядит как отсутствие работы.
- **Теги:** goal:service-observability
Двигает пункт 1 «Завершения» цели: по метрикам видно, что конвейер встал, и это
отличимо от «работы нет».
Сегодня метрики считают события — принято, распознано, отказано, — а состояние
очереди не считает ничто. Пустая очередь и очередь, где десять задач висят третий
час, дают одинаковый нулевой прирост счётчиков.
## Затрагивает
- `internal/metrics` — метрика числа задач по состояниям и возраста самой старой
из них;
- таблица задач: колонка состояния, по которой считаются числа, и отметка
времени перехода, по которой считается возраст самой старой;
- `docs/architecture.md`, раздел эксплуатации — что означает каждое число;
- `docs/database.md` — частота съёма чисел.
## Критерии приёмки
- Число задач в каждом состоянии видно на `/metrics`. Оракул — тест: завести три
задачи в разных состояниях и снять метрики.
- Возраст самой старой задачи в состоянии виден числом и растёт, пока задача не
сменила состояние. Оракул — тест на задаче с заведомо старой отметкой времени.
- Съём чисел не мешает работе воркеров. Оракул — чтение кода: запрос идёт по
индексу состояния и не берёт захват.
## Рамки
Правило оповещения на этой метрике заводит задача `owner-alerting` — здесь
только число. Порог «сколько считается застреванием» здесь не решается: он
зависит от замеров цели `long-recordings`.
+1 -1
View File
@@ -1,7 +1,7 @@
# 🧹 Разобрать мелочи слоя хранилища
- **Тип:** chore
- **Категория:** Очередь
- **Категория:** Очередь — Мелочи слоя хранилища разбираются, пока слой в руках.
- **Зачем:** Три мелочи ниже потолка триажа: цикл воркера пишет потерю захвата уровнем ERROR и считает её отказом, тип ошибки заведён там, где конвенция просит sentinel, а FileName несёт два разных смысла.
Все три пришли ревью задачи `pocketbase-storage` 2026-08-12 и остались ниже
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Сопоставить пользователя Telegram с учётной записью
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Связывать чат с учётной записью не с чем, пока у записи нет владельца.
- **Зачем:** Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
- **Теги:** goal:multi-user
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Приложение показывает, о чём запись, не читая её целиком
- **Тип:** goal
- **Секция:** Направления
- **Секция:** Запланировано — Очередь у цели появилась: три задачи подряд после настроек, и прибавка видна даже в боте.
- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
- **Теги:** decomposed
+5 -4
View File
@@ -1,7 +1,7 @@
# ✨ Сделать экран загрузки записи и её состояния
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Первое, ради чего приложение открывают; требует каркаса и контракта, оба выше.
- **Зачем:** Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
- **Теги:** goal:web-access
@@ -28,9 +28,10 @@
тест: после ответа `done` новых запросов к API нет.
- Отказ задачи показывается человекочитаемым текстом, а не кодом и не сырой
ошибкой. Оракул — тест на ответе с состоянием `failed`.
- Файл больше предела отклоняется на экране до отправки, с названным числом.
Оракул — тест на файле сверх предела: запроса к API нет, на экране предел
числом.
- Файл больше предела отклоняется на экране до отправки, с названным числом, и
предел экран берёт у сервера, а не держит своей константой. Оракул — тест на
файле сверх предела: запроса на загрузку нет, на экране предел числом, и
смена предела на стороне сервера меняет это число.
## Рамки
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Показывать ход загрузки записи на экране
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Ход загрузки — доработка того же экрана.
- **Зачем:** Гигабайтный файл уходит на сервер молча: до ответа сервера экран не отличает идущую загрузку от зависшей.
- **Теги:** goal:upload-reliability
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую
- **Тип:** goal
- **Секция:** Запланировано
- **Секция:** Запланировано — Дедупликация и пачка файлов доводятся на готовом экране загрузки и экономят деньги с первого дня приложения.
- **Зачем:** Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги.
- **Теги:** decomposed
+1 -1
View File
@@ -1,7 +1,7 @@
# ✨ Считать объём, минуты и расход по каждому пользователю
- **Тип:** feature
- **Категория:** Очередь
- **Категория:** Очередь — Учёт по пользователям требует владельца записи и метрик расхода — оба выше.
- **Зачем:** Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего.
- **Теги:** goal:usage-stats
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Владелец видит, кто сколько загрузил и во что это обошлось
- **Тип:** goal
- **Секция:** Сопровождение
- **Секция:** Сопровождение — Учёт по пользователям требует владельца записи и метрик расхода — оба появляются раньше.
- **Зачем:** Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется.
- **Теги:** decomposed
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Пользователь настраивает, что сервис делает с его записями
- **Тип:** goal
- **Секция:** Запланировано
- **Секция:** Запланировано — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений.
- **Зачем:** Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем.
- **Теги:** decomposed
+42
View File
@@ -0,0 +1,42 @@
# ✨ Принимать видео и брать из него звуковую дорожку
- **Тип:** feature
- **Категория:** Очередь — Приём видео и отказ неизвестному формату ставятся на замеренный перечень.
- **Зачем:** Запись семейного архива приходит видеофайлом, а приём смотрит на аудио: человеку приходится доставать дорожку самому.
- **Теги:** goal:any-audio-source
Двигает пункты 2 и 3 «Завершения» цели: видеофайл принимается и из него берётся
звуковая дорожка, а формат, который принять нельзя, отклоняется на приёме
понятным текстом, а не отказом на конвертации через минуту.
Берётся после `audio-format-coverage-measure`: чем отклонять неизвестное, пока
неизвестно, что конвейер берёт.
## Затрагивает
- приём обоими входами: перечень допустимых типов содержимого и расширений;
- адаптер метаданных — распознавание дорожек в контейнере;
- команда конвертера: выбор звуковой дорожки из видео;
- метка формата в метрике — перечень известных форматов расширяется
([ADR](../../docs/adr/ADR-2026-08-11-known-format-label.md));
- `docs/passport.md` — граница «что принимаем» сдвигается на видео;
- `openspec/specs/intake/` — требование о принимаемых форматах.
## Критерии приёмки
- Видеофайл с одной звуковой дорожкой доходит до текста. Оракул — тест приёма и
конвертации на своём коротком видео из временного каталога.
- Видео без звуковой дорожки отклоняется на приёме понятным текстом и задачи не
заводит. Оракул — тест: файл без звука, ответ с причиной, в очереди пусто.
- Формат вне замеренного перечня отклоняется на приёме, а не висит в конвейере.
Оракул — тест на файле неизвестного типа: отказ приходит сразу.
- Расширение в журнале и метка в метрике остаются в перечне известных форматов.
Оракул — тест: на видео с новым контейнером в журнал уходит расширение из
перечня, а не сырое расширение файла, и метка метрики берётся из того же
перечня.
## Рамки
Видео с несколькими звуковыми дорожками берём первую, выбор дорожки человеком за
задачей не стоит. Потолок длины остаётся тем, что назначит цель
`long-recordings`.
+1 -1
View File
@@ -1,7 +1,7 @@
# 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон
- **Тип:** goal
- **Секция:** Запланировано
- **Секция:** Запланировано — Экраны приложения нужны раньше настроек, удаления, уровней текста и статистики: всем им негде показаться.
- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
- **Теги:** decomposed