Files
transcriber/docs/passport.md
T
av c9b7765646 хранилище переехало с PocketBase на SQLite со своим каталогом файлов
- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose
  под файловым замком, одна миграция начальной схемы вместо семи прежних
- транспорт переписан на net/http: свои слои, свой ограничитель частоты,
  отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли
- по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета
  читается справа налево, узнавание известного идёт читающим пулом
2026-08-23 08:06:04 +03:00

12 KiB
Raw Blame History

Паспорт проекта

Зачем это и для кого. architecture.md отвечает «как устроено», tasks/BACKLOG.md — «в каком порядке», паспорт — «зачем и для кого».

Цель

Превращать записанную речь в текст, который можно читать и искать, и хранить этот текст вместе с записью.

Сервис — архив, и это решено 2026-08-11. Прежде граница читалась «отдаём текст и на этом заканчиваем»; теперь расшифровки и исходные записи лежат бессрочно, а список отбирается по темам. Причина в основном сценарии: семейный архив загружают один раз, а возвращаются к нему годами.

Потребители — список закрытый: он определяет, что считать нужным, а что интересным.

Кто Что ему нужно от нас
Владелец сервиса Загрузить диктофонную запись или видео из семейного архива с телефона и получить текст. Видеть, кто сколько загрузил и во что это обошлось
Приглашённый пользователь Войти в приложение через свою учётную запись, загрузить запись, забрать текст, вернуться к ней через месяц. Приложение ставится на телефон; каждый видит только свои записи
Внешняя программа Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Сегодня не работает вовсе: домен целиком стоит за обратным прокси, и запрос программы отбивает он, не доходя до сервиса. Токен и правило прокси мимо входа приносит api-tokens

Вход у сервиса один — HTTP API, и приложение строится поверх него. До 2026-08-11 основным входом был Telegram-бот. 2026-08-11 основным объявили приложение: диктофонная запись на несколько часов через Telegram не проходит вовсе. 2026-08-14 бот убран целиком — временно, до задачи, которая свяжет чат с учётной записью. Вместе с ним из потребителей ушёл пользователь Telegram.

Цель достигнута, когда:

  • запись любого распространённого формата принимается без предварительной подготовки, включая дорожку из видео;
  • запись расчётного потолка — шести часов — доходит до текста, а не прерывается ошибкой при достижении предела (норма — openspec/specs/storage);
  • сервисом пользуются несколько человек, и записи одного не видны другому;
  • текст доступен там же, где загружали. Человек узнаёт о его готовности, не держа приложение открытым;
  • расшифровка не теряется: к записи возвращаются через месяц и находят её по заголовку и темам;
  • владелец видит расход по каждому пользователю и понимает, во что обходится приглашение ещё одного человека.

Что целью не является

Граница домена. По ней в теме architecture судят, не перенесено ли понятие через границу.

  • Правка текста руками. Машинную вычитку расшифровки отдаём — литературный текст стоит рядом с сырым, — а редактором не становимся: текст руками не правим, не размечаем и не экспортируем в форматы документов. Граница сдвинута 2026-08-11: до того запрет читался «расшифровку отдаём как есть».
  • Разговор о записи. Ответы на вопросы по содержанию и поиск по смыслу — за границей. Заголовок, пересказ и темы внутри границы: она сдвинута 2026-08-10, и до того запись читалась «мы отдаём текст, а не выводы из него». Считать уровни текста берётся задача llm-insights-adapter.
  • Собственные модели. Не обучаем и не держим у себя ни модель распознавания, ни языковую модель: и речь, и выводы из текста считает внешний сервис.
  • Управление учётными записями. Пользователей заводит и проверяет внешний провайдер, свою регистрацию и свои пароли не делаем. Своя строка учётной записи у сервиса при этом есть, и границы это не двигает: сервис зеркалит имя, названное провайдером, — заводит строку при первом обращении под новым именем и связывает с ней записи владельца. Кто этот человек и пускать ли его, сервис не решает никогда. Исключений у этого больше нет: панель администратора со своим паролем владельца жила здесь с 2026-08-11 по 2026-08-22 и ушла вместе со встроенным хранилищем — своего входа сервис не ведёт вовсе.
  • Живая расшифровка. Работаем с готовой записью, поток в реальном времени не обрабатываем.
  • Диктофон. Запись звука делает телефон, а приложение принимает готовый файл. Своей записи и работы без сети не делаем.
  • Файловое хранилище общего назначения. Храним аудио и видео, отданные ради речи в них. Складом произвольных файлов и папками сервис не становится. Общего доступа к чужим записям целью нет, и с 2026-08-14 его нет и на деле: у записи есть владелец, и чужую по её идентификатору не отдают (security.md, «Периметр»). Закрыла это задача record-ownership.
  • Учёт денег. Считаем объём, минуты и токены по каждому пользователю и показываем их владельцу. Цен, счетов и отказов по исчерпании квоты не делаем: пользователя, потратившего слишком много, останавливает разговор или отзыв доступа в Authelia.

Типовые сценарии

Первые два — основные, и сегодня не работает ни один. Приложение с 2026-08-15 есть, но экранов у него пока нет: оно открывается и показывает вошедшего, а загрузку и список заводят upload-and-status-screen и records-list-screen.

  1. Семейный архив. Человек открывает приложение на телефоне, выбирает до десяти записей разом — диктофонные дорожки и видео, — и закрывает его. Загрузка показывает ход. Файл, который уже загружали, не грузится второй раз. Когда текст готов, приходит уведомление; в списке запись видна заголовком и темами.
  2. Возвращение к записи. Через месяц человек открывает список, находит запись по заголовку или теме и читает вычитанный текст, а при нужде — сырую расшифровку.
  3. Загрузка по HTTP. Программа шлёт POST /app/audiorecords со своим токеном, получает идентификатор записи и читает её карточку GET /app/audiorecords/{id}, пока не увидит done; текст забирает отдельным адресом GET /app/audiorecords/{id}/text. Сегодня доступно только тому, кого назвал доверенный источник: неузнанный запрос всеми адресами отклоняется. Своего способа представиться у программы нет — его заводит api-tokens. Записи при этом разграничены: видны только записи того, чьим именем пришли.
  4. Отказ на середине. Конвертация или распознавание не удались — запись получает признак остановки с причиной, и карточка записи отдаёт признак и причину тому, кто её загрузил. Сообщения о неудаче сервис никому не шлёт: доставка ушла вместе с ботом, а уведомления заводит задача ntfy-delivery.

Референсы

Где смотреть prior art, когда упёрлись.

  • Yandex SpeechKit, отложенное распознавание — модель deferred-general, которой пользуемся: она и задаёт потолок по длине записи и формату.
  • Whisper и его серверные обёртки — запасной путь, если внешний сервис перестанет устраивать по цене или по качеству русской речи. PocketBase побывала и референсом, и стеком, и ушла из проекта целиком. Референсом она быть перестала 2026-08-12, когда задача pocketbase-storage перевела её в стек; стеком — 2026-08-22, когда задача storage-without-pocketbase (adr) убрала её вместе с панелью владельца и собственным адресным пространством. Хранилище у сервиса своё: SQLite напрямую и файлы записей своим каталогом. Схема и раскладка — database.md.