- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug: заголовки входа подставляет слой транспорта, второго процесса локальный запуск больше не требует - подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает сам сервис - адресного предохранителя нет по решению владельца — цена названа в ADR и в модели угроз
135 lines
13 KiB
Markdown
135 lines
13 KiB
Markdown
# Паспорт проекта
|
||
|
||
Зачем это и для кого. [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).
|