Files
transcriber/docs/passport.md
T
av 52fe31319a локальный вход задаётся конфигом: заголовки подставляет сам сервис
- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug:
  заголовки входа подставляет слой транспорта, второго процесса локальный запуск
  больше не требует
- подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает
  сам сервис
- адресного предохранителя нет по решению владельца — цена названа в ADR и в
  модели угроз
2026-08-23 13:12:47 +03:00

135 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/BACKLOG.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](../tasks/items/llm-insights-adapter.md).
- **Собственные модели.** Не обучаем и не держим у себя ни модель распознавания,
ни языковую модель: и речь, и выводы из текста считает внешний сервис.
- **Управление учётными записями.** Пользователей заводит и проверяет внешний
провайдер, свою регистрацию и свои пароли не делаем. Своя строка учётной
записи у сервиса при этом есть, и границы это не двигает: сервис **зеркалит**
имя, названное провайдером, — заводит строку при первом обращении под новым
именем и связывает с ней записи владельца. Кто этот человек и пускать ли его,
сервис не решает никогда. Панель администратора со своим паролем владельца жила
здесь с 2026-08-11 по 2026-08-22 и ушла вместе со встроенным хранилищем.
*Изъятие одно:* при включённом предохранителе `[server] debug`, выключенном по
умолчанию, сервис подставляет запросу те заголовки входа, которые в бою даёт
обратный прокси. Своего входа, регистрации и проверки допуска он от этого не
заводит: подставленное имя проходит то же узнавание, что и пришедшее. Кого
пускать, провайдер решает во всяком прогоне без изъятия; в самом изъятии его не
спрашивают вовсе — сервис называет пришедшего сам. Тем изъятие и держится
выключенным умолчанием, а границу его держит спека
[access](../openspec/specs/access/spec.md).
- **Живая расшифровка.** Работаем с готовой записью, поток в реальном времени не
обрабатываем.
- **Диктофон.** Запись звука делает телефон, а приложение принимает готовый
файл. Своей записи и работы без сети не делаем.
- **Файловое хранилище общего назначения.** Храним аудио и видео, отданные ради
речи в них. Складом произвольных файлов и папками сервис не становится. Общего
доступа к чужим записям целью нет, и с 2026-08-14 его нет и на деле: у записи
есть владелец, и чужую по её идентификатору не отдают
([security.md](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](adr/ADR-2026-08-22-storage-without-pocketbase.md)) убрала её вместе с
панелью владельца и собственным адресным пространством. Хранилище у сервиса своё:
SQLite напрямую и файлы записей своим каталогом. Схема и раскладка —
[database.md](database.md).