From 5501384cdcb0f594f06a29ab17c47434c9e76c12 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Thu, 13 Aug 2026 16:00:14 +0300 Subject: [PATCH] =?UTF-8?q?tasks:=20=D1=83=D0=BF=D1=80=D0=B0=D0=B7=D0=B4?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=8B=20=D1=86=D0=B5=D0=BB=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=80=D0=BE=D0=B0=D0=B4=D0=BC=D0=B0=D0=BF,=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=8A=D1=8F=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=B4=D0=B8=D1=8F=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 11 записей типа goal закрыты с причиной, называющей задачи-наследники; ROADMAP.md удалён, индекс остался один — BACKLOG.md; - 33 записи переписаны: ссылка «Двигает пункты N «Завершения» цели» уступила место прямому утверждению — без целей номера пунктов вели в никуда; - шапка BACKLOG.md размечена парой , порядок строк теперь объявлен зависимостью, а не важностью. --- .av-dev.toml | 3 +- tasks/BACKLOG.md | 36 ++++++++------- tasks/REJECTED.md | 11 +++++ tasks/ROADMAP.md | 46 -------------------- tasks/items/admin-stats-screen.md | 5 +-- tasks/items/any-audio-source.md | 20 --------- tasks/items/api-tokens.md | 6 +-- tasks/items/audio-format-coverage-measure.md | 6 +-- tasks/items/data-ownership.md | 27 ------------ tasks/items/dedup-by-content-hash.md | 4 +- tasks/items/delete-record.md | 6 +-- tasks/items/email-notification.md | 5 +-- tasks/items/external-service-metrics.md | 5 +-- tasks/items/insights-visible-in-list.md | 11 +++-- tasks/items/installable-pwa.md | 10 ++--- tasks/items/intake-limits-measure.md | 7 ++- tasks/items/job-path-by-request.md | 6 +-- tasks/items/json-api-for-spa.md | 5 +-- tasks/items/literary-text-level.md | 5 +-- tasks/items/llm-insights-adapter.md | 6 +-- tasks/items/long-audio-chunking.md | 7 ++- tasks/items/long-recordings.md | 28 ------------ tasks/items/long-text-delivery.md | 4 +- tasks/items/multi-file-upload.md | 4 +- tasks/items/multi-user.md | 24 ---------- tasks/items/ntfy-delivery.md | 13 +++--- tasks/items/opentelemetry-fit.md | 1 - tasks/items/orphan-file-on-failed-intake.md | 2 +- tasks/items/owner-alerting.md | 5 +-- tasks/items/play-recording-in-app.md | 6 +-- tasks/items/ready-notification.md | 28 ------------ tasks/items/record-ownership.md | 5 +-- tasks/items/records-list-screen.md | 5 +-- tasks/items/reject-oversized-recording.md | 5 +-- tasks/items/service-observability.md | 26 ----------- tasks/items/settings-applied-in-pipeline.md | 6 +-- tasks/items/settings-screen.md | 6 +-- tasks/items/spa-skeleton.md | 9 ++-- tasks/items/speechkit-limits.md | 7 ++- tasks/items/stalled-pipeline-metric.md | 6 +-- tasks/items/telegram-account-link.md | 6 +-- tasks/items/text-insights.md | 36 --------------- tasks/items/upload-and-status-screen.md | 5 +-- tasks/items/upload-progress.md | 4 +- tasks/items/upload-reliability.md | 25 ----------- tasks/items/usage-accounting.md | 11 +++-- tasks/items/usage-stats.md | 25 ----------- tasks/items/user-settings.md | 22 ---------- tasks/items/video-audio-track-intake.md | 11 +++-- tasks/items/web-access.md | 31 ------------- 50 files changed, 118 insertions(+), 485 deletions(-) delete mode 100644 tasks/ROADMAP.md delete mode 100644 tasks/items/any-audio-source.md delete mode 100644 tasks/items/data-ownership.md delete mode 100644 tasks/items/long-recordings.md delete mode 100644 tasks/items/multi-user.md delete mode 100644 tasks/items/ready-notification.md delete mode 100644 tasks/items/service-observability.md delete mode 100644 tasks/items/text-insights.md delete mode 100644 tasks/items/upload-reliability.md delete mode 100644 tasks/items/usage-stats.md delete mode 100644 tasks/items/user-settings.md delete mode 100644 tasks/items/web-access.md diff --git a/.av-dev.toml b/.av-dev.toml index 91a89d8..f203f39 100644 --- a/.av-dev.toml +++ b/.av-dev.toml @@ -1,7 +1,7 @@ # Раскладка av-dev в этом проекте: версия и настройки проверок. # Файл ведут скиллы плагина, править руками можно — комментарии свои. -version = 1 # версия раскладки; обратной совместимости нет, есть «приведён» и «нет» +version = 3 # версия раскладки; обратной совместимости нет, есть «приведён» и «нет» [docs] # каталог миграций: по нему docs.py сверяет схему с database.md @@ -10,3 +10,4 @@ migrations = "internal/adapter/repo/pocketbase/migrations" [tasks] # каталог задач от корня репозитория; имена частей — умолчания скрипта dir = "tasks" +stage = "build" diff --git a/tasks/BACKLOG.md b/tasks/BACKLOG.md index 035439b..3e8a02f 100644 --- a/tasks/BACKLOG.md +++ b/tasks/BACKLOG.md @@ -1,23 +1,27 @@ # Беклог Что **можно взять**. Одна задача = один файл `items/.md` -+ строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что берут, -роадмап — то, подо что берут. **Порядок строк значим:** -это очередь, и первая строка — то, что делают следующим. Порядок -назначает человек на груминге, машина его не выводит. Одно исключение -производно от типа — сырьё (`research` без раздела «Вопрос») -стоит в конце: его не берут. Ведётся скиллом `av-dev:task-track`. ++ строка здесь. Ведётся скиллом `av-dev:task-track`. -Секция одна — полок домена у проекта нет, и делить очередь на две -значило бы держать два порядка вместо одного. + +Стадия проекта — **стройка** (`[tasks] stage = "build"`). +**Порядок строк — зависимость:** это план стройки от базы к деталям, +и строка выше сделана раньше не потому, что важнее, а потому, что +иначе нельзя. Секция здесь **одна**: разложенный по полкам план +перестаёт быть планом. Список пишется вперёд целиком — это не +гниение беклога, а замысел. Пустой беклог значит, что стройка +окончена: дальше `tasks.py stage support`. + -**Чем очередь упорядочена на этом этапе — от базы к деталям.** -Сначала то, на чём стоит остальное: проверки, которым можно верить, -владелец записи, единый контракт API, покрытый тестами конвейер, — и -только потом экраны и возможности поверх них. Порядок расставлен на -груминге 2026-08-12 и держится, пока сервис не собран целиком: -задача, взятая раньше своего основания, стоит дважды — сперва её -пишут, потом переписывают под появившееся основание. +**Чем основание отличается от детали в этом проекте.** Сначала идёт то, +на чём стоит остальное: проверки, которым можно верить, владелец записи, +единый контракт API, покрытый тестами конвейер, — и только потом экраны +и возможности поверх них. Этот порядок расставили 2026-08-12. +Задача, взятая раньше своего основания, стоит дважды: сперва её пишут, +потом переписывают под появившееся основание. + +Одно место в очереди назначено не человеком, а типом: сырьё +(`research` без раздела «Вопрос») стоит в конце секции — его не берут. Отсюда правило для **новых** записей. Заведённая по ходу работы — интейком, урожаем ревью, разбором находок — задача встаёт в конец @@ -76,7 +80,7 @@ - [✨ Считать только те уровни текста, что включены у владельца записи](items/settings-applied-in-pipeline.md) — Дом настроек есть, а конвейер их не читает: выключенный уровень всё равно уходит платной модели, и настройка ничего не экономит. - [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана. - [✨ Слать готовый текст на почту из учётной записи](items/email-notification.md) — Адрес почты приходит вместе с входом через OIDC, но почтового отправителя в сервисе нет. -- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается. +- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а работа над долгими записями без них не начинается. - [🔬 Потолки приёма, конвертации и заливки по длине записи](items/intake-limits-measure.md) — Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно. - [✨ Отклонять на приёме запись сверх потолка](items/reject-oversized-recording.md) — Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста. - [✨ Резать длинную запись на фрагменты и продолжать с места остановки](items/long-audio-chunking.md) — Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз. diff --git a/tasks/REJECTED.md b/tasks/REJECTED.md index 5d17218..97bb5fa 100644 --- a/tasks/REJECTED.md +++ b/tasks/REJECTED.md @@ -6,3 +6,14 @@ - 2026-08-12 `gate-go-version-sync` — 🧹 Сверять версию Go в образе с директивой go.mod. Причина: слита в go-1-26-upgrade 2026-08-12: сверка версии и само обновление правят одни и те же строки go.mod и Dockerfile, и порознь заводят расхождение заново. Была секция: Очередь. +- 2026-08-13 `any-audio-source` — 🎯 Принимается запись любого формата, включая дорожку из видео. Причина: Зонтик над разобранной работой: перечень форматов меряет audio-format-coverage-measure, дорожку из видео берёт video-audio-track-intake. Тип goal упразднён раскладкой av-dev 3. Была секция: Направления. +- 2026-08-13 `data-ownership` — 🎯 Человек убирает свою запись из архива вместе со всеми текстами. Причина: Зонтик над разобранной работой: удаление записи со всеми уровнями текста делает delete-record. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `long-recordings` — 🎯 Запись длиной до шести часов доходит до текста. Причина: Зонтик над разобранной работой: потолки меряют speechkit-limits и intake-limits-measure, дальше идут reject-oversized-recording, long-audio-chunking и long-text-delivery. Тип goal упразднён раскладкой av-dev 3. Была секция: Направления. +- 2026-08-13 `multi-user` — 🎯 Сервисом пользуются несколько человек, и записи одного не видны другому. Причина: Зонтик над разобранной работой: вход сделан задачей oidc-login, дальше идут record-ownership, telegram-account-link и api-tokens. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `ready-notification` — 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым. Причина: Зонтик над разобранной работой: доставку делают ntfy-delivery и email-notification. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `service-observability` — 🎯 Состояние сервиса видно без чтения логов. Причина: Зонтик над разобранной работой: словарь метрик выбирает opentelemetry-fit, дальше идут stalled-pipeline-metric, external-service-metrics, job-path-by-request и owner-alerting. Тип goal упразднён раскладкой av-dev 3. Была секция: Сопровождение. +- 2026-08-13 `text-insights` — 🎯 Приложение показывает, о чём запись, не читая её целиком. Причина: Зонтик над разобранной работой: уровни текста считает llm-insights-adapter, вычитку даёт literary-text-level, показывает их insights-visible-in-list. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `upload-reliability` — 🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую. Причина: Зонтик над разобранной работой: дедупликацию делает dedup-by-content-hash, пачку файлов multi-file-upload, ход загрузки upload-progress, уборку за обрывом orphan-file-on-failed-intake. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `usage-stats` — 🎯 Владелец видит, кто сколько загрузил и во что это обошлось. Причина: Зонтик над разобранной работой: учёт ведёт usage-accounting, показывает его admin-stats-screen. Тип goal упразднён раскладкой av-dev 3. Была секция: Сопровождение. +- 2026-08-13 `user-settings` — 🎯 Пользователь настраивает, что сервис делает с его записями. Причина: Зонтик над разобранной работой: дом настроек заводит settings-screen, читает их в конвейере settings-applied-in-pipeline. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. +- 2026-08-13 `web-access` — 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон. Причина: Зонтик над разобранной работой: каркас даёт spa-skeleton, контракт json-api-for-spa, экраны upload-and-status-screen, records-list-screen, play-recording-in-app, установку на телефон installable-pwa. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано. diff --git a/tasks/ROADMAP.md b/tasks/ROADMAP.md deleted file mode 100644 index b231d97..0000000 --- a/tasks/ROADMAP.md +++ /dev/null @@ -1,46 +0,0 @@ -# Роадмап - -Состояние проекта: что приложение **уже умеет** и чего ещё не умеет. -Цель — возможность приложения: файл типа `goal` (🎯) в -`items/`. Её задачи здесь **не перечисляются** — перечень даёт -`tasks.py list --goal <слаг>`. - -- **Запланировано** — очередь значима и обосновывается прозой; -- **Направления** — очереди нет, тянутся долго; -- **Сопровождение** — чем держат проект: инструмент, - процесс, эксплуатация. Не возможности приложения, и отдельно — - чтобы не читаться как обещание продукта; -- **Готово** — достигнутое: строку пишет - `tasks.py close <цель> --implemented`, ссылки на файл в ней нет — - файл удаляется, поведение живёт в спеках. Стоит последней: копится. - -Секции **канонические** и переименованию проектом не подлежат: -у каждой свой смысл, и в достигнутое пишет сам `close`. Порядок -тоже канонический. Английский -вариант — Planned | Directions | Operations | Done, один язык на весь -индекс. - -## Запланировано - -- [🎯 Сервисом пользуются несколько человек, и записи одного не видны другому](items/multi-user.md) — У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки. -- [🎯 Записи загружаются и читаются в приложении, которое ставится на телефон](items/web-access.md) — Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем. -- [🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую](items/upload-reliability.md) — Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги. -- [🎯 Человек убирает свою запись из архива вместе со всеми текстами](items/data-ownership.md) — Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда. -- [🎯 Пользователь настраивает, что сервис делает с его записями](items/user-settings.md) — Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем. -- [🎯 Приложение показывает, о чём запись, не читая её целиком](items/text-insights.md) — Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем. -- [🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым](items/ready-notification.md) — Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться. - -## Направления - -- [🎯 Запись длиной до шести часов доходит до текста](items/long-recordings.md) — Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, границы модели deferred-general неизвестны, а перезапуск на середине начинает распознавание заново. -- [🎯 Принимается запись любого формата, включая дорожку из видео](items/any-audio-source.md) — Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил. - -## Сопровождение - -- [🎯 Состояние сервиса видно без чтения логов](items/service-observability.md) — Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера. -- [🎯 Владелец видит, кто сколько загрузил и во что это обошлось](items/usage-stats.md) — Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется. - -## Готово - -- 2025-08-14 `telegram-transcription` — Голосовое сообщение из Telegram возвращается текстом. Первый вход сервиса: бот принимает голосовое, аудиофайл и документ с аудио и отвечает расшифровкой. -- 2025-08-08 `api-transcription` — Запись, отданная по HTTP, возвращается текстом. Программный вход: файл отдаётся формой, готовность и текст забираются опросом статуса задачи. diff --git a/tasks/items/admin-stats-screen.md b/tasks/items/admin-stats-screen.md index 5b1a00d..13f1b87 100644 --- a/tasks/items/admin-stats-screen.md +++ b/tasks/items/admin-stats-screen.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Страница показывает собранный учёт: без учёта показывать нечего. - **Зачем:** Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет. -- **Теги:** goal:usage-stats -Двигает пункты 1, 2 и 3 «Завершения» цели: расход по каждому пользователю виден -на странице, и открывается она только владельцу сервиса. +Расход по каждому пользователю виден на странице, и открывается она только +владельцу сервиса. ## Затрагивает diff --git a/tasks/items/any-audio-source.md b/tasks/items/any-audio-source.md deleted file mode 100644 index 1dbedd9..0000000 --- a/tasks/items/any-audio-source.md +++ /dev/null @@ -1,20 +0,0 @@ -# 🎯 Принимается запись любого формата, включая дорожку из видео - -- **Тип:** goal -- **Секция:** Направления — Перечень форматов не замерен, и потолок длины у видео тот же, что у долгих записей: тянется следом за ними. -- **Зачем:** Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил. -- **Теги:** decomposed - -Человек отдаёт файл, не думая о том, что внутри: аудио любого распространённого -контейнера или видео, из которого нужна только речь. Подготовка на стороне -пользователя не требуется. - -## Завершение - -1. Перечень принимаемых форматов замерен и записан в `research/`, а не выведен - из документации ffmpeg. -2. Видеофайл принимается, и из него берётся звуковая дорожка. -3. Формат, который принять нельзя, отклоняется на приёме — с текстом, из - которого понятно почему, а не отказом на конвертации через минуту. -4. Расхождение ogg/vorbis против заявленного SpeechKit `OGG_OPUS` разобрано: - либо устранено, либо записано как проверенно безвредное. diff --git a/tasks/items/api-tokens.md b/tasks/items/api-tokens.md index 9aaad92..49d1cb2 100644 --- a/tasks/items/api-tokens.md +++ b/tasks/items/api-tokens.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Второй способ представиться ставится на готовые владельца и контракт, иначе форма ошибки переписывается дважды. - **Зачем:** Вход через OIDC закрывает API целиком, а скрипту браузерная сессия недоступна: автоматизировать загрузку станет нечем. -- **Теги:** goal:multi-user -Двигает пункты 1 и 6 «Завершения» цели: запрос без токена не проходит (пункт 1), -а скрипт ходит в API по токену, выпущенному пользователем, и видит ровно его -записи (пункт 6). +Запрос без токена не проходит, а скрипт ходит в API по токену, выпущенному +пользователем, и видит ровно его записи. Токен принадлежит учётной записи и даёт ровно её права: записи, заведённые по токену, видны владельцу в приложении, и наоборот. diff --git a/tasks/items/audio-format-coverage-measure.md b/tasks/items/audio-format-coverage-measure.md index b62434f..bbadacd 100644 --- a/tasks/items/audio-format-coverage-measure.md +++ b/tasks/items/audio-format-coverage-measure.md @@ -3,11 +3,9 @@ - **Тип:** research - **Категория:** Очередь — Форматы: сначала замер того, что конвейер берёт на самом деле. - **Зачем:** Команда ffmpeg проверена на голосовых Telegram, а что она берёт помимо них, не мерил никто: перечень выведен из документации, а не из прогона. -- **Теги:** goal:any-audio-source -Двигает пункты 1 и 4 «Завершения» цели: перечень принимаемых форматов замерен и -записан, а расхождение `ogg/vorbis` против заявленного SpeechKit `OGG_OPUS` -разобрано. +Замер даёт перечень принимаемых форматов и разбирает расхождение `ogg/vorbis` +против заявленного SpeechKit `OGG_OPUS`. ## Вопрос diff --git a/tasks/items/data-ownership.md b/tasks/items/data-ownership.md deleted file mode 100644 index ac93fe5..0000000 --- a/tasks/items/data-ownership.md +++ /dev/null @@ -1,27 +0,0 @@ -# 🎯 Человек убирает свою запись из архива вместе со всеми текстами - -- **Тип:** goal -- **Секция:** Запланировано — Очередь у цели появилась: удаление записи стоит 32-й строкой и трогает конвейер, файлы и колонку дедупликации, которые к тому месту готовы. -- **Зачем:** Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда. -- **Теги:** decomposed - -Сервис объявлен архивом 2026-08-11, и с тем же решением у человека появляется -обратное право: сказать «убери это» и убедиться, что убрано. Речь в записи -принадлежит тем, кто говорил, а не хранилищу. - -Стирается всё, что породила запись: сам файл, его фрагменты, объект в Object -Storage и все уровни текста. **Учёт расхода при этом остаётся** — деньги уже -потрачены, и сводка владельца задним числом не переписывается; строки -потребления несут идентификаторы и числа, не текст. - -## Завершение - -1. Своя запись убирается одним действием, и после него не остаётся ни файла, ни - объекта в хранилище, ни одного из уровней текста. -2. Убранное не возвращается: восстановления нет, и человек предупреждён об этом - до подтверждения. -3. Чужую запись убрать нельзя — ни по идентификатору, ни по токену. -4. Сводка расхода после удаления не меняется: потраченное остаётся видно - владельцу сервиса. -5. Тот же файл, загруженный снова, обрабатывается как новая запись, а не - узнаётся дедупликацией удалённой. diff --git a/tasks/items/dedup-by-content-hash.md b/tasks/items/dedup-by-content-hash.md index d4857a4..92b2971 100644 --- a/tasks/items/dedup-by-content-hash.md +++ b/tasks/items/dedup-by-content-hash.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Дедупликация ищет совпадение в пределах пользователя — то есть после владельца записи, и экономит деньги с первого дня приложения. - **Зачем:** Один и тот же файл, отправленный дважды, распознаётся дважды и оплачивается дважды: приём не смотрит на содержимое вовсе. -- **Теги:** goal:upload-reliability -Двигает пункт 1 «Завершения» цели: повторная отправка того же файла возвращает -прежнюю запись вместо второй задачи. +Повторная отправка того же файла возвращает прежнюю запись вместо второй задачи. Совпадение ищется **в пределах одного пользователя**: чужая расшифровка по совпадению хеш-суммы не отдаётся и о её существовании отправитель не узнаёт. diff --git a/tasks/items/delete-record.md b/tasks/items/delete-record.md index bd9e6ed..0107cd8 100644 --- a/tasks/items/delete-record.md +++ b/tasks/items/delete-record.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Удаление трогает конвейер, файлы, объект хранилища и колонку дедупликации — всё это к этому месту уже готово. - **Зачем:** Ни файлы, ни расшифровки не удаляются вовсе: убрать запись сегодня можно только руками в базе и в каталоге на сервере. -- **Теги:** goal:data-ownership -Двигает все пять пунктов «Завершения» цели: запись убирается одним действием -вместе с файлом, объектом в хранилище и всеми уровнями текста. Чужую запись -убрать нельзя. Учёт расхода остаётся. +Запись убирается одним действием вместе с файлом, объектом в хранилище и всеми +уровнями текста. Чужую запись убрать нельзя. Учёт расхода остаётся. Удаление необратимо и потому спрашивает подтверждения. Задача, которая ещё в работе, тоже убирается: конвейер обязан заметить исчезнувшую запись и не diff --git a/tasks/items/email-notification.md b/tasks/items/email-notification.md index b5ba52f..0512812 100644 --- a/tasks/items/email-notification.md +++ b/tasks/items/email-notification.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Второй канал на той же доставке. - **Зачем:** Адрес почты приходит вместе с входом через OIDC, но почтового отправителя в сервисе нет. -- **Теги:** goal:ready-notification -Двигает пункты 1, 2 и 3 «Завершения» цели: готовый текст и отказ доходят -письмом, а адрес берётся у учётной записи, а не из общего конфига. +Готовый текст и отказ доходят письмом, а адрес берётся у учётной записи, а не из +общего конфига. Почта — второй канал рядом с тем, что заводит `ntfy-delivery`; выбор канала остаётся в той же единой точке, что и сейчас. diff --git a/tasks/items/external-service-metrics.md b/tasks/items/external-service-metrics.md index 396c1e3..ed5df38 100644 --- a/tasks/items/external-service-metrics.md +++ b/tasks/items/external-service-metrics.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Метрики внешних сервисов пишутся в выбранном словаре, а не переписываются потом. - **Зачем:** Ни у Telegram, ни у Object Storage, ни у SpeechKit нет ни одной метрики: отказ внешнего сервиса виден только строкой в журнале контейнера. -- **Теги:** goal:service-observability -Двигает пункты 2 и 5 «Завершения» цели: у каждого внешнего сервиса появляются -вызовы, отказы и длительность, а расход на платные сервисы виден числом. +У каждого внешнего сервиса появляются вызовы, отказы и длительность, а расход на +платные сервисы становится виден числом. Внешних сервисов сегодня четыре — Telegram, Object Storage, SpeechKit и `ffmpeg`/`ffprobe` как внешний процесс; пятым станет языковая модель. Метрика diff --git a/tasks/items/insights-visible-in-list.md b/tasks/items/insights-visible-in-list.md index b5ebda8..06499a0 100644 --- a/tasks/items/insights-visible-in-list.md +++ b/tasks/items/insights-visible-in-list.md @@ -1,14 +1,13 @@ # ✨ Показывать заголовок в списке, отбирать список по темам и считать токены - **Тип:** feature -- **Категория:** Очередь — Три пункта «Завершения» цели не закрывала ни одна задача: показывать и отбирать можно, когда заголовки и темы уже считаются. +- **Категория:** Очередь — Показывать и отбирать можно, когда заголовки и темы уже считаются. - **Зачем:** Заголовок, темы и пересказ считаются, но список по-прежнему показывает первые слова расшифровки и не отбирается ничем, а расход на модель не виден числом. -- **Теги:** goal:text-insights -Двигает пункты 1, 4 и 6 «Завершения» цели — те три, где выводы из текста -становятся видны человеку и владельцу: заголовок в списке (1), отбор по темам -(4), стоимость числом (6). Сами уровни считает `llm-insights-adapter`, показать -их некому: экран списка написан раньше и знает только первые слова расшифровки. +Выводы из текста становятся видны человеку и владельцу: заголовок в списке, +отбор по темам, стоимость числом. Сами уровни считает `llm-insights-adapter`, +показать их некому: экран списка написан раньше и знает только первые слова +расшифровки. Берётся после `llm-insights-adapter`: пока заголовков и тем нет, показывать и отбирать нечего. diff --git a/tasks/items/installable-pwa.md b/tasks/items/installable-pwa.md index 2292c53..4f593be 100644 --- a/tasks/items/installable-pwa.md +++ b/tasks/items/installable-pwa.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Ставить на телефон есть смысл, когда есть что ставить. - **Зачем:** Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет. -- **Теги:** goal:web-access -Двигает пункты 5 и 6 «Завершения» цели: приложение ставится с телефона и -запускается с ярлыка без адресной строки, а открытое без сети показывает это -состоянием. +Приложение ставится с телефона и запускается с ярлыка без адресной строки, а +открытое без сети показывает это состоянием. Берётся после того, как есть что ставить, — то есть после `records-list-screen`. @@ -41,5 +39,5 @@ ## Рамки Офлайн-чтения готовых расшифровок и очереди отправки без сети **не делаем** — -это за границей цели. Web Push не делаем: уведомления идут через apprise и ntfy, -цель `ready-notification`. +это за границей из паспорта. Web Push не делаем: уведомления идут через apprise +и ntfy — задача `ntfy-delivery`. diff --git a/tasks/items/intake-limits-measure.md b/tasks/items/intake-limits-measure.md index 529b2f5..61f9de1 100644 --- a/tasks/items/intake-limits-measure.md +++ b/tasks/items/intake-limits-measure.md @@ -3,11 +3,10 @@ - **Тип:** research - **Категория:** Очередь — Второй замер — остальные четыре звена. - **Зачем:** Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно. -- **Теги:** goal:long-recordings -Пункт 1 «Завершения» цели требует замера пяти звеньев, а разведка -`speechkit-limits` меряет одно — модель `deferred-general`. Остальные четыре -дешевле: они не требуют боевых ключей и считаются локально, кроме заливки. +Звеньев пять, а разведка `speechkit-limits` меряет одно — модель +`deferred-general`. Остальные четыре дешевле: они не требуют боевых ключей и +считаются локально, кроме заливки. Числа нужны раньше кода: они назначают потолок, который проверяет приём, и длину фрагмента, на которые режет `long-audio-chunking`. diff --git a/tasks/items/job-path-by-request.md b/tasks/items/job-path-by-request.md index 295606b..1a7c2b7 100644 --- a/tasks/items/job-path-by-request.md +++ b/tasks/items/job-path-by-request.md @@ -1,12 +1,10 @@ # ✨ Собирать путь одной записи по конвейеру запросом - **Тип:** feature -- **Категория:** Очередь — Пункт 3 «Завершения» цели не закрывала ни одна задача; применяет словарь, который выберет разведка строкой выше. +- **Категория:** Очередь — Применяет словарь метрик, который выберет разведка строкой выше. - **Зачем:** Звенья пути связаны только идентификатором задачи в строках журнала: чтобы понять, где запись провела минуты, владелец читает логи контейнера глазами. -- **Теги:** goal:service-observability -Двигает пункт 3 «Завершения» цели: путь одной записи по конвейеру собирается -запросом, а не чтением логов глазами. +Путь одной записи по конвейеру собирается запросом, а не чтением логов глазами. Путь длиной в минуты идёт через четыре внешних сервиса и три воркера. Сегодня его звенья связывает `job_id` в строках журнала, и собирает их человек. diff --git a/tasks/items/json-api-for-spa.md b/tasks/items/json-api-for-spa.md index 6131dac..d261acb 100644 --- a/tasks/items/json-api-for-spa.md +++ b/tasks/items/json-api-for-spa.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Единая точка трансляции доменной ошибки — база и для экранов, и для токенов; список своих записей заводится после владельца, а не до. - **Зачем:** Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем. -- **Теги:** goal:web-access -Двигает пункты 1, 2 и 4 «Завершения» цели: экраны заводят задачу, видят её -состояние и листают список — всё через один контракт. +Экраны заводят задачу, видят её состояние и листают список — всё через один +контракт. Обработчик `GET /api/status/:id` сегодня отвечает `404` на **любую** ошибку чтения, включая сбой базы, а `POST /api/audio` — `500` на любую ошибку заведения, diff --git a/tasks/items/literary-text-level.md b/tasks/items/literary-text-level.md index 0cb62b3..a5fd87b 100644 --- a/tasks/items/literary-text-level.md +++ b/tasks/items/literary-text-level.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Вычитанный текст считается тем же адаптером. - **Зачем:** Сырая расшифровка идёт без знаков препинания, с повторами и словами-паразитами: читать её подряд тяжело, а другого уровня текста нет. -- **Теги:** goal:text-insights -Двигает пункт «Завершения» цели про литературный текст: у записи появляется -второй уровень — тот же разговор, вычитанный до читаемого вида. +У записи появляется второй уровень текста — тот же разговор, вычитанный до +читаемого вида. Вычитку считает та же внешняя модель, что заголовок и темы. Сырой текст остаётся и не переписывается: уровни лежат рядом, а не поверх друг друга. diff --git a/tasks/items/llm-insights-adapter.md b/tasks/items/llm-insights-adapter.md index 796a279..70f85e9 100644 --- a/tasks/items/llm-insights-adapter.md +++ b/tasks/items/llm-insights-adapter.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Уровни текста: сюда приходит пятая внешняя зависимость, и конвейер к этому моменту покрыт тестами. - **Зачем:** Расшифровка доходит стеной текста: ни заголовка, ни тем, ни пересказа сервис не считает, и клиента языковой модели в нём нет. -- **Теги:** goal:text-insights -Двигает пункты 1, 3, 4 и 5 «Завершения» цели: у готовой записи появляются -заголовок (1), пересказ (3) и темы (4), а отказ и молчание модели не роняют -задачу (5). +У готовой записи появляются заголовок, пересказ и темы, а отказ и молчание +модели не роняют задачу. Здесь появляется пятая внешняя зависимость — языковая модель с OpenAI-совместимым интерфейсом за шлюзом bifrost, — и текст расшифровки уходит diff --git a/tasks/items/long-audio-chunking.md b/tasks/items/long-audio-chunking.md index 622eca7..b5a8466 100644 --- a/tasks/items/long-audio-chunking.md +++ b/tasks/items/long-audio-chunking.md @@ -3,11 +3,10 @@ - **Тип:** feature - **Категория:** Очередь — Резка на фрагменты — самая глубокая переделка конвейера, и она идёт по замеренным числам. - **Зачем:** Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз. -- **Теги:** goal:long-recordings -Двигает пункты 3, 5 и 6 «Завершения» цели: запись в пределах потолка доходит до -текста целиком, долгая задача не занимает воркер на часы, а перезапуск на -середине продолжает работу с первого неотмеченного фрагмента. +Запись в пределах потолка доходит до текста целиком, долгая задача не занимает +воркер на часы, а перезапуск на середине продолжает работу с первого +неотмеченного фрагмента. Запись делится на фрагменты, каждый распознаётся отдельно, готовый фрагмент отмечается в базе. После перезапуска работа продолжается с первого неотмеченного, а diff --git a/tasks/items/long-recordings.md b/tasks/items/long-recordings.md deleted file mode 100644 index 37977b5..0000000 --- a/tasks/items/long-recordings.md +++ /dev/null @@ -1,28 +0,0 @@ -# 🎯 Запись длиной до шести часов доходит до текста - -- **Тип:** goal -- **Секция:** Направления — Цель начинается с двух замеров и кончается резкой на фрагменты — самой глубокой переделкой конвейера: очереди внутри нет, пока числа не получены. -- **Зачем:** Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, границы модели deferred-general неизвестны, а перезапуск на середине начинает распознавание заново. -- **Теги:** decomposed - -Лекция, созвон, интервью и диктофонная запись из семейного архива целиком -превращаются в текст. Сегодня неизвестно даже, на каком звене такая запись -отваливается, — цель начинается с замера, а не с переделки. - -Расчётный потолок — **шесть часов**: он взят с запасом под диктофонные записи и -дорожки из видео, и замер проверяет, каким звеном он ограничен на самом деле. - -## Завершение - -1. Потолки каждого звена замерены и записаны в `research/` с командой замера: - приём из Telegram, приём по HTTP, конвертация, заливка в Object Storage, - модель `deferred-general`. -2. Запись, превышающая потолок, отклоняется на приёме понятным текстом, а не - висит в конвейере до истечения захвата. -3. Запись в пределах потолка доходит до текста и не теряет его хвост. -4. Текст в несколько сотен килобайт доходит до получателя: и в браузере, и в - Telegram, где предел сообщения — 4000 символов. -5. Долгая задача не блокирует короткие: запись на три часа не останавливает - конвейер для голосового на десять секунд. -6. Перезапуск сервиса на середине долгой расшифровки не начинает её заново: - работа продолжается с места остановки. diff --git a/tasks/items/long-text-delivery.md b/tasks/items/long-text-delivery.md index 77b380c..b78b7e2 100644 --- a/tasks/items/long-text-delivery.md +++ b/tasks/items/long-text-delivery.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Сотни килобайт текста появляются только после долгих записей. - **Зачем:** Отправитель Telegram режет текст по 4000 знаков: расшифровка шестичасовой записи придёт сотней сообщений подряд. -- **Теги:** goal:long-recordings -Двигает пункт 4 «Завершения» цели: текст в несколько сотен килобайт доходит и в -Telegram, и в браузере. +Текст в несколько сотен килобайт доходит и в Telegram, и в браузере. Деление по словам (`internal/adapter/telegram/split.go`) остаётся для обычной расшифровки; сверх названного числа частей вместо потока сообщений уходит один diff --git a/tasks/items/multi-file-upload.md b/tasks/items/multi-file-upload.md index f8eeb64..db8e1e8 100644 --- a/tasks/items/multi-file-upload.md +++ b/tasks/items/multi-file-upload.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Пачка файлов заводится на готовом экране загрузки и готовой дедупликации. - **Зачем:** Приём берёт один файл в запросе, а с телефона выбирают пачку сразу: десять записей значат десять заходов на экран загрузки. -- **Теги:** goal:upload-reliability -Двигает пункт 2 «Завершения» цели: пачка файлов уходит одной загрузкой, и отказ -одного не отменяет остальные. +Пачка файлов уходит одной загрузкой, и отказ одного не отменяет остальные. ## Затрагивает diff --git a/tasks/items/multi-user.md b/tasks/items/multi-user.md deleted file mode 100644 index a7f6468..0000000 --- a/tasks/items/multi-user.md +++ /dev/null @@ -1,24 +0,0 @@ -# 🎯 Сервисом пользуются несколько человек, и записи одного не видны другому - -- **Тип:** goal -- **Секция:** Запланировано — Владелец записи — фундамент, на котором стоят список своих записей, дедупликация, удаление, учёт расхода и квота: пока его нет, остальные цели строятся на песке. -- **Зачем:** У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки. -- **Теги:** decomposed - -Приложение узнаёт, кто к нему пришёл, и показывает каждому только его записи. -Учётные записи заводит и проверяет внешний провайдер — Authelia по OIDC; своей -регистрации и своих паролей не делаем, это граница из -[паспорта](../../docs/passport.md). - -## Завершение - -1. Неаутентифицированный запрос к записям не проходит: ни к странице, ни к API. -2. У задачи и файла есть владелец, и выборка чужой записи по её - идентификатору возвращает «не найдено», а не содержимое. -3. Вход идёт через OIDC у Authelia; выход из сессии работает. -4. Пользователь Telegram сопоставлен с учётной записью, и записи, пришедшие - ботом, видны ему же в браузере. -5. Белый список Telegram перестаёт быть отдельным механизмом: право писать боту - выводится из учётной записи. -6. Скрипт ходит в API по токену, выпущенному пользователем, и видит ровно его - записи. diff --git a/tasks/items/ntfy-delivery.md b/tasks/items/ntfy-delivery.md index f8dab4a..c166518 100644 --- a/tasks/items/ntfy-delivery.md +++ b/tasks/items/ntfy-delivery.md @@ -3,12 +3,10 @@ - **Тип:** feature - **Категория:** Очередь — Канал уведомлений выбирается в настройках, которые уже есть. - **Зачем:** Пользователь веба узнаёт о готовности только опросом с открытого экрана. -- **Теги:** goal:ready-notification -Двигает пункты 1, 2, 4 и 5 «Завершения» цели: готовый текст и отказ доходят до -пользователя веба без открытого приложения, отказ канала задачу не роняет, а -пользователь Telegram получает ответ по-прежнему ботом. Выбор канала самим -пользователем (пункт 3) заводит `settings-screen`. +Готовый текст и отказ доходят до пользователя веба без открытого приложения, +отказ канала задачу не роняет, а пользователь Telegram получает ответ +по-прежнему ботом. Выбор канала самим пользователем заводит `settings-screen`. Сегодня `completeJob` и `failJob` отвечают только источнику `telegram`; источник `api` не получает ничего. Здесь появляется второй способ доставки, и @@ -46,3 +44,8 @@ Web Push с VAPID и своим хранением подписок не делаем. Своего сервера ntfy не поднимаем — адрес приходит конфигом. Текст расшифровки уходит на внешний сервис, и это сдвиг периметра: строка в `docs/security.md` обязательна. + +Отказ от Web Push сегодня живёт открытым вопросом `docs/architecture.md`, +«Уведомления», и своего ADR не имеет: заводить его не из чего, пока нет +`design.md` этой задачи. Решение промоутится из него, когда задача пойдёт в +работу. diff --git a/tasks/items/opentelemetry-fit.md b/tasks/items/opentelemetry-fit.md index 2a67bc2..2c081ac 100644 --- a/tasks/items/opentelemetry-fit.md +++ b/tasks/items/opentelemetry-fit.md @@ -3,7 +3,6 @@ - **Тип:** research - **Категория:** Очередь — Сопровождение: словарь метрик выбирается до того, как метрик станет втрое больше. - **Зачем:** Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами. -- **Теги:** goal:service-observability Эндпоинт `/metrics` остаётся и развивается — это решено. Вопрос в том, чем его развивать: дописывать счётчики в `internal/metrics` напрямую через diff --git a/tasks/items/orphan-file-on-failed-intake.md b/tasks/items/orphan-file-on-failed-intake.md index 1980186..55b4374 100644 --- a/tasks/items/orphan-file-on-failed-intake.md +++ b/tasks/items/orphan-file-on-failed-intake.md @@ -3,7 +3,7 @@ - **Тип:** fix - **Категория:** Очередь — Осиротевший файл — тот же отказ на середине, и чинится тем же местом приёма. - **Зачем:** Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками. -- **Теги:** review-2026-08-11, goal:upload-reliability +- **Теги:** review-2026-08-11 Приём пишет файл на диск, потом спрашивает у источника метаданных длительность, потом заводит запись в учёте и задачу. Уборка при отказе есть **только на diff --git a/tasks/items/owner-alerting.md b/tasks/items/owner-alerting.md index 5542d54..371f35c 100644 --- a/tasks/items/owner-alerting.md +++ b/tasks/items/owner-alerting.md @@ -3,9 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Правило оповещения ставится на метрику, которой до этого нет. - **Зачем:** Об отказе владелец узнаёт от пользователя: правил оповещения нет ни на одной метрике, а метрики читают глазами. -- **Теги:** goal:service-observability -Двигает пункт 4 «Завершения» цели: владелец узнаёт об отказе сам. +Владелец узнаёт об отказе сам. Берётся после `external-service-metrics` и `stalled-pipeline-metric`: правило оповещения ставится на метрику, а метрик, на которых его ставить, сегодня нет. @@ -35,4 +34,4 @@ Выкладку правил запускает человек — `inv pl -- transcriber` из `pet-project-server`. Пользовательские уведомления о готовности здесь не -трогаем: это цель `ready-notification`. +трогаем: их доставку заводят `ntfy-delivery` и `email-notification`. diff --git a/tasks/items/play-recording-in-app.md b/tasks/items/play-recording-in-app.md index ec93402..073c703 100644 --- a/tasks/items/play-recording-in-app.md +++ b/tasks/items/play-recording-in-app.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Проигрывание живёт на экране записи, которого до списка нет. - **Зачем:** Послушать загруженное приложение не даёт, а самой копии для этого у задачи нет: указатель на файл перезаписывается на каждом шаге конвейера и у готовой задачи ведёт на объект в Object Storage. -- **Теги:** goal:web-access -Двигает пункт 7 «Завершения» цели: запись слушается на её экране, а не -скачивается файлом. +Запись слушается на её экране, а не скачивается файлом. Заметка владельца от 2026-08-12. Копии на диске остаются все три — исходник, ogg после конвертации и запись об объекте, — но `job.FileID` на каждом шаге @@ -47,6 +45,6 @@ ogg после конвертации и запись об объекте, — ## Рамки Скачивания файла, обрезки и синхронизации текста со звуком не делаем. Записи -звука в приложении не делаем — это граница цели. Формат хранения и раскладку +звука в приложении не делаем — это граница из паспорта. Формат хранения и раскладку каталога данных не трогаем: они объявлены необратимыми, а шаг схемы, уехавший на сервер, не переписывается — изменение только новым файлом шага. diff --git a/tasks/items/ready-notification.md b/tasks/items/ready-notification.md deleted file mode 100644 index d635af6..0000000 --- a/tasks/items/ready-notification.md +++ /dev/null @@ -1,28 +0,0 @@ -# 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым - -- **Тип:** goal -- **Секция:** Запланировано — Канал уведомлений выбирается в настройках, которые к этому месту уже есть. -- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться. -- **Теги:** decomposed - -Задача уходит в работу — человек закрывает приложение и получает сообщение, -когда текст готов. Пользователь Telegram это уже имеет: бот отвечает сам. -Пользователь веба — нет. - -Каналов два: почта, адрес которой приходит вместе с входом через OIDC, и -Telegram для того, кто связал свою учётную запись с ботом. Доставку берём -внешнюю: apprise как отправитель, ntfy как один из каналов. Web Push с VAPID и -собственным хранением подписок за целью **не стоит** — это отдельная -инфраструктура ради того же результата. - -## Завершение - -1. Готовый текст доходит до пользователя веба сообщением, без открытого - приложения. -2. Отказ задачи доходит тем же путём и тем же человекочитаемым текстом, что - видит пользователь Telegram. -3. Канал и адрес уведомлений задаёт пользователь, а не общий конфиг: у каждого - свой, и от уведомлений можно отказаться. -4. Недоступность канала уведомлений не роняет задачу и не мешает ей завершиться: - текст остаётся в приложении. -5. Пользователь Telegram получает ответ по-прежнему ботом, а не вторым каналом. diff --git a/tasks/items/record-ownership.md b/tasks/items/record-ownership.md index f516bfe..51b1013 100644 --- a/tasks/items/record-ownership.md +++ b/tasks/items/record-ownership.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Фундамент домена: без владельца знание UUID и есть право читать чужое, и на владельце стоят список, дедуп, удаление, учёт и квота. - **Зачем:** У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать. -- **Теги:** goal:multi-user -Двигает пункт 2 «Завершения» цели: выборка чужой записи по её идентификатору -возвращает «не найдено», а не содержимое. +Выборка чужой записи по её идентификатору возвращает «не найдено», а не +содержимое. Берётся после `oidc-login`: до входа неизвестно, кто владелец. diff --git a/tasks/items/records-list-screen.md b/tasks/items/records-list-screen.md index f322a5e..50741b5 100644 --- a/tasks/items/records-list-screen.md +++ b/tasks/items/records-list-screen.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Список и чтение текста берутся после экрана загрузки — так записано в самой задаче. - **Зачем:** Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем. -- **Теги:** goal:web-access -Двигает пункты 3 и 4 «Завершения» цели: список своих записей открывается и -листается, а готовый текст читается и копируется целиком. +Список своих записей открывается и листается, а готовый текст читается и +копируется целиком. Берётся после `upload-and-status-screen`. diff --git a/tasks/items/reject-oversized-recording.md b/tasks/items/reject-oversized-recording.md index b6462b4..b73715b 100644 --- a/tasks/items/reject-oversized-recording.md +++ b/tasks/items/reject-oversized-recording.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Отклонять сверх потолка можно, когда потолок замерен. - **Зачем:** Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста. -- **Теги:** goal:long-recordings -Двигает пункт 2 «Завершения» цели: запись длиннее потолка отклоняется на приёме -понятным текстом, а не отказом через час. +Запись длиннее потолка отклоняется на приёме понятным текстом, а не отказом +через час. Потолок задаётся конфигурацией, а не зашивается в код: числа приходят из разведок `intake-limits-measure` и `speechkit-limits`, и меняются они без сборки. diff --git a/tasks/items/service-observability.md b/tasks/items/service-observability.md deleted file mode 100644 index 712f459..0000000 --- a/tasks/items/service-observability.md +++ /dev/null @@ -1,26 +0,0 @@ -# 🎯 Состояние сервиса видно без чтения логов - -- **Тип:** goal -- **Секция:** Сопровождение — Словарь метрик выбирается разведкой до того, как метрик станет втрое больше; наблюдаем мы, а не пользователь сервиса. -- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера. -- **Теги:** decomposed - -Наблюдаем **мы**, а не пользователь сервиса, — поэтому цель стоит в -сопровождении, а не среди возможностей приложения. Пользовательская половина -той же темы живёт отдельно: о готовности своей записи человек узнаёт по цели -[ready-notification](ready-notification.md). - -Эндпоинт `/metrics` остаётся и развивается. Чем именно развивать — голым -`client_golang` или OpenTelemetry с трассировкой — решает разведка -`opentelemetry-fit`. - -## Завершение - -1. По метрикам видно, что конвейер встал: задача висит в состоянии дольше - обычного, и это отличимо от «работы нет». -2. Каждый внешний сервис имеет метрику вызовов, отказов и длительности — - сегодня их нет ни у одного. -3. Путь одной записи по конвейеру собирается запросом, а не чтением логов - глазами. -4. Владелец узнаёт об отказе сам, а не от пользователя. -5. Стоимость обращений к платным сервисам видна числом. diff --git a/tasks/items/settings-applied-in-pipeline.md b/tasks/items/settings-applied-in-pipeline.md index 34ea10c..bb186cf 100644 --- a/tasks/items/settings-applied-in-pipeline.md +++ b/tasks/items/settings-applied-in-pipeline.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Выключать нечего, пока уровней нет; настройка ставится сразу за ними, чтобы платная модель не считала лишнее. - **Зачем:** Дом настроек есть, а конвейер их не читает: выключенный уровень всё равно уходит платной модели, и настройка ничего не экономит. -- **Теги:** goal:user-settings -Двигает пункты 1 и 4 «Завершения» цели: выключенный уровень не запрашивается у -языковой модели вовсе, а запись обрабатывается по той настройке, которая -действовала на приёме. +Выключенный уровень не запрашивается у языковой модели вовсе, а запись +обрабатывается по той настройке, которая действовала на приёме. Берётся после `settings-screen` (настройки негде хранить) и после `llm-insights-adapter` (нечего выключать). diff --git a/tasks/items/settings-screen.md b/tasks/items/settings-screen.md index f4e57be..69f3c20 100644 --- a/tasks/items/settings-screen.md +++ b/tasks/items/settings-screen.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений. - **Зачем:** Настроек у пользователя нет вовсе: уровни текста и канал уведомлений задаются общим конфигом сервиса. -- **Теги:** goal:user-settings -Двигает пункты 1, 2 и 3 «Завершения» цели: у каждого пользователя свои -переключатели уровней текста и свой канал уведомлений, и они переживают -повторный вход. +У каждого пользователя свои переключатели уровней текста и свой канал +уведомлений, и они переживают повторный вход. Здесь появляется дом настроек — таблица и эндпоинты; применяют их задачи уровней текста и доставки уведомлений. diff --git a/tasks/items/spa-skeleton.md b/tasks/items/spa-skeleton.md index ca37a66..5215dd8 100644 --- a/tasks/items/spa-skeleton.md +++ b/tasks/items/spa-skeleton.md @@ -3,12 +3,11 @@ - **Тип:** feature - **Категория:** Очередь — Каркас приложения: экранов нет и собирать их нечем, а на экране стоят настройки, удаление, уровни текста и статистика. - **Зачем:** Экранов нет и собирать их нечем: ни сборки фронтенда, ни раздачи статики в проекте не существует. -- **Теги:** goal:web-access -Ни одной строки «Завершения» цели каркас не закрывает: он готовит четыре -экранные — 1, 2, 3 и 4, — но сам по себе пользователю ничего не даёт. Видимое от -него одно: приложение открывается и достаёт данные с живого сервера, а не отдаёт -пустую страницу. +Каркас готовит четыре экрана — загрузку, состояние задачи, чтение текста и +список, — но сам по себе пользователю ничего не даёт. Видимое от него одно: +приложение открывается и достаёт данные с живого сервера, а не отдаёт пустую +страницу. Фреймворк выбран 2026-08-11 — Vue 3 с роутером пятой версии и сборкой Vite ([ADR](../../docs/adr/ADR-2026-08-11-spa-on-vue.md)); правила кода приложения diff --git a/tasks/items/speechkit-limits.md b/tasks/items/speechkit-limits.md index aaa41ca..2454add 100644 --- a/tasks/items/speechkit-limits.md +++ b/tasks/items/speechkit-limits.md @@ -2,11 +2,10 @@ - **Тип:** research - **Категория:** Очередь — Долгие записи начинаются с замера: без потолков ни отказ на приёме, ни резка не проектируются. -- **Зачем:** Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается. -- **Теги:** goal:long-recordings +- **Зачем:** Потолок длины записи и перечень принимаемых форматов неизвестны, а работа над долгими записями без них не начинается. -Цель «запись длиной в несколько часов доходит до текста» упирается в то, что -неизвестно, на каком звене такая запись отваливается. Начинать с переделки +Запись длиной в несколько часов упирается в то, что неизвестно, на каком звене +она отваливается. Начинать с переделки приёма или с деления записи на куски — разные работы, и выбор между ними определяет замер, а не рассуждение. diff --git a/tasks/items/stalled-pipeline-metric.md b/tasks/items/stalled-pipeline-metric.md index c0b47ae..5c90080 100644 --- a/tasks/items/stalled-pipeline-metric.md +++ b/tasks/items/stalled-pipeline-metric.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Признак вставшего конвейера — вторая половина той же работы. - **Зачем:** Вставший конвейер неотличим от простоя: возраст задачи в состоянии не считается, и очередь без движения выглядит как отсутствие работы. -- **Теги:** goal:service-observability -Двигает пункт 1 «Завершения» цели: по метрикам видно, что конвейер встал, и это -отличимо от «работы нет». +По метрикам видно, что конвейер встал, и это отличимо от «работы нет». Сегодня метрики считают события — принято, распознано, отказано, — а состояние очереди не считает ничто. Пустая очередь и очередь, где десять задач висят третий @@ -34,4 +32,4 @@ Правило оповещения на этой метрике заводит задача `owner-alerting` — здесь только число. Порог «сколько считается застреванием» здесь не решается: он -зависит от замеров цели `long-recordings`. +зависит от замеров `speechkit-limits` и `intake-limits-measure`. diff --git a/tasks/items/telegram-account-link.md b/tasks/items/telegram-account-link.md index 4771322..8e09b3a 100644 --- a/tasks/items/telegram-account-link.md +++ b/tasks/items/telegram-account-link.md @@ -3,11 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Связывать чат с учётной записью не с чем, пока у записи нет владельца. - **Зачем:** Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны. -- **Теги:** goal:multi-user -Двигает пункты 4 и 5 «Завершения» цели: записи из бота видны тому же человеку в -приложении, а белый список перестаёт быть отдельным механизмом — право писать -боту выводится из учётной записи. +Записи из бота видны тому же человеку в приложении, а белый список перестаёт +быть отдельным механизмом — право писать боту выводится из учётной записи. Берётся после `record-ownership`: связывать не с чем, пока у записи нет владельца. diff --git a/tasks/items/text-insights.md b/tasks/items/text-insights.md deleted file mode 100644 index f127b50..0000000 --- a/tasks/items/text-insights.md +++ /dev/null @@ -1,36 +0,0 @@ -# 🎯 Приложение показывает, о чём запись, не читая её целиком - -- **Тип:** goal -- **Секция:** Запланировано — Очередь у цели появилась: три задачи подряд после настроек, и прибавка видна даже в боте. -- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем. -- **Теги:** decomposed - -Текст записи выдаётся уровнями: сырая расшифровка, вычитанный литературный -текст, заголовок в одну строку, темы-теги и пересказ в два-три предложения. -Считает их внешний сервис с OpenAI-совместимым интерфейсом за шлюзом bifrost — -своей модели не держим, как не держим и модели распознавания. - -Каждый уровень сверх сырого пользователь выключает у себя — это цель -[user-settings](user-settings.md). - -**Это сдвиг границы паспорта, и сдвигали её дважды.** До 2026-08-10 «понимание -сказанного» было записано как то, чем проект не является: «мы отдаём текст, а не -выводы из него». 2026-08-11 к выводам добавился литературный текст — машинная -вычитка расшифровки, прежде запрещённая строкой про редактор. - -От обоих запретов остались более узкие: ответов на вопросы по записи и поиска по -смыслу не делаем, правку текста руками человеку не даём. - -## Завершение - -1. У готовой записи есть заголовок в одну строку, и он виден в списке вместо - первых слов расшифровки. -2. Рядом с сырой расшифровкой лежит вычитанный текст: со знаками препинания, без - повторов и слов-паразитов. -3. У записи есть пересказ в два-три предложения, который читается быстрее самой - расшифровки. -4. У записи есть темы, и по ним список отбирается. -5. Отказ или молчание внешнего сервиса не роняют задачу: расшифровка доходит до - человека без выводов из неё. -6. Стоимость обращения к сервису видна метрикой: сколько записей обработано и - сколько это стоило по числу токенов. diff --git a/tasks/items/upload-and-status-screen.md b/tasks/items/upload-and-status-screen.md index cd9f2e9..4bba510 100644 --- a/tasks/items/upload-and-status-screen.md +++ b/tasks/items/upload-and-status-screen.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Первое, ради чего приложение открывают; требует каркаса и контракта, оба выше. - **Зачем:** Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит. -- **Теги:** goal:web-access -Двигает пункты 1 и 2 «Завершения» цели: экран принимает файл и заводит задачу, а -её состояние обновляется само, пока задача не дошла до `done` или `failed`. +Экран принимает файл и заводит задачу, а её состояние обновляется само, пока +задача не дошла до `done` или `failed`. Берётся после `spa-skeleton` и `json-api-for-spa`. diff --git a/tasks/items/upload-progress.md b/tasks/items/upload-progress.md index e1d463f..3cc8962 100644 --- a/tasks/items/upload-progress.md +++ b/tasks/items/upload-progress.md @@ -3,10 +3,8 @@ - **Тип:** feature - **Категория:** Очередь — Ход загрузки — доработка того же экрана. - **Зачем:** Гигабайтный файл уходит на сервер молча: до ответа сервера экран не отличает идущую загрузку от зависшей. -- **Теги:** goal:upload-reliability -Двигает пункт 3 «Завершения» цели: ход загрузки виден числом, а не одним -ожиданием. +Ход загрузки виден числом, а не одним ожиданием. Загрузка шестичасовой записи по сотовой сети идёт минутами. Пока сервер не ответил, экран показывает долю отправленного и позволяет её отменить. diff --git a/tasks/items/upload-reliability.md b/tasks/items/upload-reliability.md deleted file mode 100644 index 2dfc579..0000000 --- a/tasks/items/upload-reliability.md +++ /dev/null @@ -1,25 +0,0 @@ -# 🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую - -- **Тип:** goal -- **Секция:** Запланировано — Дедупликация и пачка файлов доводятся на готовом экране загрузки и экономят деньги с первого дня приложения. -- **Зачем:** Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги. -- **Теги:** decomposed - -Человек отдаёт диктофонную запись или видео из семейного архива с телефона, по -сотовой сети, и загрузка либо доходит, либо честно говорит, что не дошла. -Отданное однажды второй раз не грузится и второй раз не распознаётся. - -Дедупликация ищет совпадение **в пределах одного пользователя**: чужая -расшифровка не достаётся по совпадению хеш-суммы, даже когда файл тот же. - -Загрузка частями за целью пока **не стоит**: сначала один запрос, а протокол -докачки разбирает разведка. - -## Завершение - -1. Файл, уже загруженный этим пользователем, узнаётся по хеш-сумме: сервис - возвращает прежнюю запись и не заводит вторую задачу. -2. До десяти файлов уходят одной загрузкой, и отказ одного не отменяет - остальные. -3. Ход загрузки виден на экране числом, а не одним ожиданием. -4. Оборванная загрузка не оставляет ни файла в хранилище, ни задачи в очереди. diff --git a/tasks/items/usage-accounting.md b/tasks/items/usage-accounting.md index ba29cd3..59569dd 100644 --- a/tasks/items/usage-accounting.md +++ b/tasks/items/usage-accounting.md @@ -3,10 +3,9 @@ - **Тип:** feature - **Категория:** Очередь — Учёт по пользователям требует владельца записи и метрик расхода — оба выше. - **Зачем:** Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего. -- **Теги:** goal:usage-stats -Двигает пункты 1, 2 и 4 «Завершения» цели: по каждому пользователю копятся -объём, минуты и расход на внешние сервисы, и повтор шага не удваивает счёт. +По каждому пользователю копятся объём, минуты и расход на внешние сервисы, и +повтор шага не удваивает счёт. Учёт ведётся записями о потреблении, а не счётчиком в строке пользователя: счётчик, увеличенный дважды при повторе шага, обратно не отматывается. @@ -38,10 +37,10 @@ ## Рамки Снаружи эта задача не видна сама по себе: ни экрана, ни эндпоинта она не -заводит — их строит `admin-stats-screen`. Тип оставлен `feature` сознательно, -как шаг цели. +заводит — их строит `admin-stats-screen`. Тип оставлен `feature` сознательно: +это первая половина работы, которую вторая делает видимой. -Потолков и отказов по исчерпании квоты не заводим — цель показывает, а не +Потолков и отказов по исчерпании квоты не заводим — учёт показывает, а не ограничивает. Пересчёт расхода в деньги здесь не делается: копятся минуты и токены, цена прайс-листа живёт вне сервиса. Данные о потреблении содержат идентификаторы, но не текст записи и не имя файла (инвариант приватности). diff --git a/tasks/items/usage-stats.md b/tasks/items/usage-stats.md deleted file mode 100644 index 514c616..0000000 --- a/tasks/items/usage-stats.md +++ /dev/null @@ -1,25 +0,0 @@ -# 🎯 Владелец видит, кто сколько загрузил и во что это обошлось - -- **Тип:** goal -- **Секция:** Сопровождение — Учёт по пользователям требует владельца записи и метрик расхода — оба появляются раньше. -- **Зачем:** Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется. -- **Теги:** decomposed - -Владелец открывает страницу и видит по каждому пользователю объём загруженного, -длительность записей в минутах и расход на внешние сервисы. Приглашая человека, -он понимает, во что это обойдётся. - -Цель **показывает, но не ограничивает**: потолков и отказов по исчерпании квоты -здесь нет — перебравшего останавливает разговор или отзыв доступа в Authelia. - -Секция — сопровождение, потому что наблюдает владелец сервиса, а не его -пользователь. - -## Завершение - -1. По каждому пользователю видны объём загруженных файлов и длительность записей - в минутах, накопительно и за период. -2. Видно, во что обошлись внешние сервисы: минуты распознавания и число токенов - языковой модели. -3. Страница открывается только владельцу, обычному пользователю она недоступна. -4. Учёт переживает перезапуск и не считает одну запись дважды при повторе шага. diff --git a/tasks/items/user-settings.md b/tasks/items/user-settings.md deleted file mode 100644 index 58e8e29..0000000 --- a/tasks/items/user-settings.md +++ /dev/null @@ -1,22 +0,0 @@ -# 🎯 Пользователь настраивает, что сервис делает с его записями - -- **Тип:** goal -- **Секция:** Запланировано — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений. -- **Зачем:** Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем. -- **Теги:** decomposed - -У каждого своя мера: одному нужна только сырая расшифровка, другому — все пять -уровней текста. Настройки принадлежат человеку, а не общему конфигу сервиса, и -переживают выход и повторный вход. - -Выключенный уровень **не считается вовсе**: настройка экономит деньги, а не -прячет готовое. - -## Завершение - -1. Каждый уровень текста сверх сырого включается и выключается отдельно, и - выключенный не запрашивается у языковой модели. -2. Канал уведомлений выбирает сам пользователь, а не общий конфиг. -3. Настройки переживают выход и повторный вход, и у каждого пользователя свои. -4. Запись, заведённая до правки настроек, обрабатывается по той настройке, - которая действовала на приёме. diff --git a/tasks/items/video-audio-track-intake.md b/tasks/items/video-audio-track-intake.md index ab78fab..e046d86 100644 --- a/tasks/items/video-audio-track-intake.md +++ b/tasks/items/video-audio-track-intake.md @@ -3,11 +3,10 @@ - **Тип:** feature - **Категория:** Очередь — Приём видео и отказ неизвестному формату ставятся на замеренный перечень. - **Зачем:** Запись семейного архива приходит видеофайлом, а приём смотрит на аудио: человеку приходится доставать дорожку самому. -- **Теги:** goal:any-audio-source -Двигает пункты 2 и 3 «Завершения» цели: видеофайл принимается и из него берётся -звуковая дорожка, а формат, который принять нельзя, отклоняется на приёме -понятным текстом, а не отказом на конвертации через минуту. +Видеофайл принимается и из него берётся звуковая дорожка, а формат, который +принять нельзя, отклоняется на приёме понятным текстом, а не отказом на +конвертации через минуту. Берётся после `audio-format-coverage-measure`: чем отклонять неизвестное, пока неизвестно, что конвейер берёт. @@ -38,5 +37,5 @@ ## Рамки Видео с несколькими звуковыми дорожками берём первую, выбор дорожки человеком за -задачей не стоит. Потолок длины остаётся тем, что назначит цель -`long-recordings`. +задачей не стоит. Потолок длины остаётся тем, что назначат замеры +`speechkit-limits` и `intake-limits-measure`. diff --git a/tasks/items/web-access.md b/tasks/items/web-access.md deleted file mode 100644 index 4cfea4e..0000000 --- a/tasks/items/web-access.md +++ /dev/null @@ -1,31 +0,0 @@ -# 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон - -- **Тип:** goal -- **Секция:** Запланировано — Экраны приложения нужны раньше настроек, удаления, уровней текста и статистики: всем им негде показаться. -- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем. -- **Теги:** decomposed - -Приложение получает поверхность, на которой запись загружают и забирают текст, -не открывая Telegram и не вызывая API руками. Ставится оно на телефон и -открывается с ярлыка, как обычное приложение. Конвейер обработки при этом -остаётся прежним — меняется вход и способ показать результат. - -**Приложение — основной вход сервиса**, бот остаётся дополнением для голосовых -сообщений. Экраны рисуются сначала под телефон, потом под широкий экран. - -Границы взяты уже: запись звука в самом приложении и работа без сети за целью -**не стоят** — файл выбирают в системном диалоге, а без сети приложение -показывает, что связи нет. - -## Завершение - -1. Экран принимает файл и заводит задачу — ту же, что заводит бот. -2. Состояние задачи видно на экране и обновляется само, пока задача не дошла до - `done` или `failed`; отказ показывается человекочитаемым текстом. -3. Готовый текст читается и копируется с экрана целиком, без деления на части. -4. Список своих записей открывается и листается. -5. Приложение ставится на телефон из браузера и запускается с ярлыка на - отдельном экране, без адресной строки. -6. Открытое без сети, приложение показывает это состоянием, а не пустой - страницей и не ошибкой браузера. -7. Загруженная запись слушается на своём экране, а не скачивается файлом.