docs: ссылки на упразднённый роадмап переадресованы, у восьми фактов назван дом

- ссылки на tasks/ROADMAP.md переведены на BACKLOG.md и на openspec/specs,
  упоминания целей — на задачи, которые эту работу делают;
- судьи документации нашли восемь расхождений: Purpose спеки pipeline объявлял
  неописанным то, что уже нормирован пятью требованиями, вид времени в
  конвенции спорил со схемой, а квоты, шесть часов и отказ от Web Push жили
  сразу в двух документах без ссылки друг на друга;
- два числа получили провенанс: 259 200 запросов в сутки и потолок в шесть
  часов теперь ведут к записке разведки, а не читаются как замер.
This commit is contained in:
av
2026-08-13 16:00:31 +03:00
parent 5501384cdc
commit 2c12376262
10 changed files with 61 additions and 40 deletions
+10 -6
View File
@@ -5,7 +5,7 @@
помечены маркером долга и переезжают туда первой же задачей, которая их трогает.
Документ описывает **сегодняшнее** устройство. Куда проект идёт — в
[passport.md](passport.md) и в [tasks/ROADMAP.md](../tasks/ROADMAP.md); что из
[passport.md](passport.md) и в [tasks/BACKLOG.md](../tasks/BACKLOG.md); что из
этого ещё не решено — в разделе «Открытые вопросы».
Заведены пять capability. Четыре первые нормируют **поведение сервиса** для его
@@ -81,7 +81,7 @@
| Отправитель Telegram | `internal/adapter/telegram` | Отправка текста, деление длинного по словам |
| Репозитории | `internal/adapter/repo/pocketbase` | Задачи и файлы коллекциями хранилища; захват — сырым запросом |
| Шаги схемы | `internal/adapter/repo/pocketbase/migrations` | Файл на шаг, имя файла — имя шага; там же имена коллекций |
| Панель владельца | `internal/adapter/repo/pocketbase`, `panel.go` | Правка задачи в панели проходит те же правила перехода, что и правка из кода |
| Панель владельца | `internal/adapter/repo/pocketbase`, `panel.go` | Панель хранилища; правила правки задачи нормирует [storage](../openspec/specs/storage/spec.md), «Владелец видит записи в панели» |
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: цепочка переходов состояний -->
@@ -192,8 +192,11 @@
текст расшифровки начинает уходить на сторону — сдвиг периметра
[security.md](security.md).
- **Долгие записи.** Потолок сегодня неизвестен и не замерялся: 20 МиБ на приём
из Telegram — точно, ограничения `deferred-general` по длине — нет. Расчётный
потолок проекта — шесть часов, и он взят с запасом, а не замером.
из Telegram — точно, ограничения `deferred-general` по длине — нет. Расчётные
шесть часов нормирует [storage](../openspec/specs/storage/spec.md), «Файл
записи живёт в хранилище»; откуда взято число —
[research/pocketbase-defaults.md](research/pocketbase-defaults.md), «Чего эта
записка не узнала».
- **Приём большого файла.** Форма читается целиком, предел памяти под multipart
задан числом в [database.md](database.md), «Настройки с числовым значением»;
обрыв начинает загрузку заново.
@@ -218,8 +221,9 @@
- **Очередь.** Модель очереди сделана задачей `pocketbase-storage` 2026-08-12
([ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md)) и нормирована
спекой `pipeline`. Не решено, отказываться ли от холостого опроса: три воркера
дают 259 200 запросов в сутки при нагрузке в единицы записей в день, и во что
это обходится, никто не мерил.
дают 259 200 запросов к базе в сутки — расчёт из паузы воркера, а не замер
([research/job-queue.md](research/job-queue.md), «Как снималось»), — при
нагрузке в единицы записей в день, и во что это обходится, никто не мерил.
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
счётчики через `client_golang` или перейти на OpenTelemetry с трассировкой —
решает разведка `opentelemetry-fit`. Коллектор был бы процессом, которого в
+5
View File
@@ -58,6 +58,11 @@
лексикографическую сортировку TEXT, то есть хронологию (`ORDER BY created_at`).
Единая точка генерации — приложение, а не умолчание в схеме: так забытая
вставка падает громко. Измерение длительности — не метка времени.
*Расхождение:* вид времени задаёт хранилище — `2006-01-02 15:04:05.000Z`,
пробел вместо `T` и доли секунды ([../database.md](../database.md), «Время»).
Правило RFC 3339 действует на то, что пишем мы сами мимо хранилища; вид
хранилища не меняем — сравнение строк в сыром запросе побайтово, и
разошедшийся вид молча обращает условие срока захвата в константу.
- Миграции — шаги PocketBase на Go
(`internal/adapter/repo/pocketbase/migrations`, файл на шаг): коллекции и их
поля заводятся кодом. При изменении структуры обновляем схему
+13 -8
View File
@@ -33,11 +33,13 @@
- **Шрифты и скрипты — со своего хоста**, без внешних. Внешних ресурсов времени
выполнения нет.
- **Офлайн-чтения расшифровок и очереди отправки без сети не делаем** — граница
цели [web-access](../../tasks/items/web-access.md). Без сети приложение
показывает состояние, а не пустой экран.
- **Web Push не делаем**: уведомления идут через apprise и ntfy, цель
[ready-notification](../../tasks/items/ready-notification.md).
- **Записи звука в приложении не делаем** — файл выбирают системным диалогом.
из [паспорта](../passport.md). Без сети приложение показывает состояние, а не
пустой экран.
- **Web Push не делаем**: уведомления идут через apprise и ntfy — решение живёт
в [architecture.md](../architecture.md), «Уведомления», делает его
[ntfy-delivery](../../tasks/items/ntfy-delivery.md).
- **Записи звука в приложении не делаем** — граница из
[паспорта](../passport.md), «Диктофон»; файл выбирают системным диалогом.
## Фреймворк и сборка
@@ -55,9 +57,12 @@
## Маршруты
- **Четыре экрана, одна таблица маршрутов** через `createRouter`. Маршруты по
файлам не включаем: сборочная надстройка роутера пятой версии стоит 34 пакета
в установке и на четырёх маршрутах не окупается.
- **Одна таблица маршрутов** через `createRouter`. Маршруты по файлам не
включаем: сборочная надстройка роутера пятой версии стоит 34 пакета в
установке и на нашем числе маршрутов не окупается
([research/spa-framework.md](../research/spa-framework.md), «Vue»). Сколько
экранов и какие — не здесь: состав нормирует спека приложения, а до неё его
держит [spa-skeleton](../../tasks/items/spa-skeleton.md).
- **Адреса обычные, а не после решётки** (`createWebHistory`). Отсюда требование
к серверу: неизвестный путь **вне** `/api/` отдаёт `index.html`, а не `404`;
пути внутри `/api/` в приложение не проваливаются никогда.
+2 -2
View File
@@ -16,8 +16,8 @@ CGO сборке не нужен.
Каталог у шагов свой, а не файл внутри пакета репозитория, и причина внешняя:
шаг гейта сверяет изменённые шаги схемы с правкой этого документа по **префиксу
пути** (`.av-dev.toml`, ключ `migrations` секции `[docs]`), а префикс наводится только на
каталог. Имена коллекций живут там же, рядом с шагом, который их заводит; пакет
пути**, а префикс наводится только на каталог. Где этот префикс задан —
[conventions/go-linters.md](conventions/go-linters.md), «Механизировано». Имена коллекций живут там же, рядом с шагом, который их заводит; пакет
репозитория берёт их оттуда.
**Идентификаторы** записей выдаёт хранилище — 15 знаков собственного алфавита.
+6 -6
View File
@@ -1,7 +1,7 @@
# Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт —
устроено», [tasks/BACKLOG.md](../tasks/BACKLOG.md) — «в каком порядке», паспорт —
«зачем и для кого».
## Цель
@@ -32,8 +32,8 @@
- запись любого распространённого формата принимается без предварительной
подготовки, включая дорожку из видео;
- запись длиной до шести часов доходит до текста, а не прерывается ошибкой при
достижении предела;
- запись расчётного потолка — шести часов доходит до текста, а не прерывается
ошибкой при достижении предела (норма — `openspec/specs/storage`);
- сервисом пользуются несколько человек, и записи одного не видны другому;
- текст доступен там же, где загружали, — в приложении и в Telegram. Человек
узнаёт о его готовности, не держа приложение открытым;
@@ -54,7 +54,8 @@
- **Разговор о записи.** Ответы на вопросы по содержанию и поиск по смыслу — за
границей. Заголовок, пересказ и темы **внутри** границы: она сдвинута
2026-08-10, и до того запись читалась «мы отдаём текст, а не выводы из него».
Направление — цель [text-insights](../tasks/items/text-insights.md).
Считать уровни текста берётся задача
[llm-insights-adapter](../tasks/items/llm-insights-adapter.md).
- **Собственные модели.** Не обучаем и не держим у себя ни модель распознавания,
ни языковую модель: и речь, и выводы из текста считает внешний сервис.
- **Управление учётными записями.** Пользователей заводит и проверяет внешний
@@ -65,8 +66,7 @@
- **Живая расшифровка.** Работаем с готовой записью, поток в реальном времени не
обрабатываем.
- **Диктофон.** Запись звука делает телефон, а приложение принимает готовый
файл. Своей записи и работы без сети не делаем — граница цели
[web-access](../tasks/items/web-access.md).
файл. Своей записи и работы без сети не делаем.
- **Файловое хранилище общего назначения.** Храним аудио и видео, отданные ради
речи в них. Складом произвольных файлов и папками сервис не становится. Общего
доступа к чужим записям целью тоже нет — но **сегодня он есть**: владельца у
+2 -1
View File
@@ -25,7 +25,8 @@ Nuxt, Next — не рассматривали: конвенция
правил, и в сборке он у всех троих совпал до байта (883 Б), что и подтверждает
одинаковость экрана.
- **Четыре маршрута** — тот же экран плюс три заглушки и переходы между ними:
столько экранов у цели [web-access](../../tasks/items/web-access.md). Роутеры
столько экранов заводит задача
[spa-skeleton](../../tasks/items/spa-skeleton.md). Роутеры
`svelte-spa-router` 5.1.1, `vue-router` 5.2.0 и 4.6.4, `react-router` 8.3.0.
- **Размеры** — `stat -c%s` и `gzip -9c | wc -c` по файлам `dist/`. Числа Vite в
своём выводе печатает по другому уровню сжатия, поэтому в таблицах ниже стоят
+8 -4
View File
@@ -151,8 +151,10 @@
ещё — самый большой.
- `conventions`: новая колонка правится во всех четырёх местах репозитория
(CLAUDE.md, «Инварианты»).
- `autotests`: покрыт ли изменённый шаг конвейера хоть одним тестом — сегодня
тестов два файла, и оба мимо конвейера.
- `autotests`: покрыт ли изменённый шаг конвейера хоть одним **проходящим**
тестом. Что уже закрыто проверками, видно по журналу дефектов ниже и по
[conventions/go-linters.md](conventions/go-linters.md), «Механизировано»;
числа файлов здесь не называем — оно протухает с каждой задачей.
- `autotests`: судит ли проверка ответа по готовому ответу, а не по изменяемому
состоянию обработчика — **только там, где ответ идёт мимо recorder**, через
свой `http.ResponseWriter`. Обращение к живой карте recorder'а с
@@ -188,8 +190,10 @@
- вход через OIDC и разграничение доступа: как связаны пользователь Telegram и
пользователь приложения, до начала работы назвать нельзя;
- всё, что делается на выбранном фреймворке впервые: форма решения нащупывается
по ходу, пока конвенция веб-UI пуста;
- всё, что делается на выбранном фреймворке впервые: правила
[conventions/web-ui.md](conventions/web-ui.md) выведены из выбора и из замера
на пробном экране, а не из написанного кода, и первая же задача проверяет их
собой — форма решения нащупывается по ходу;
- установка на телефон: service worker перехватывает запросы, и что он кэширует,
до работы назвать нельзя;
- работа с записями в несколько часов: потолки внешних сервисов не замерены,
+5 -4
View File
@@ -298,10 +298,11 @@ Telegram отправителю.
не замер: распределения длин у сервиса нет, а самая длинная проверенная запись
— 9,6 МБ ([research/pocketbase-defaults.md](research/pocketbase-defaults.md)).
Потолок длины стоит открытым вопросом `architecture.md`, «Долгие записи». Квот
нет и не будет: решено считать расход и показывать его владельцу, а не
отказывать (цель `usage-stats`). Перебравшего останавливает разговор или отзыв доступа в
Authelia. Рост каталога данных при этом ничем не наблюдается —
открытый вопрос `architecture.md`.
нет — это граница домена, [passport.md](passport.md), «Учёт денег»; расход
считают `usage-accounting` и `admin-stats-screen`. Для модели угроз отсюда
следует одно: ни числом запросов, ни размером записи вошедший не ограничен, и
защищаться от исчерпания диска мы не пытаемся. Рост каталога данных при этом
ничем не наблюдается — открытый вопрос `architecture.md`.
- **Перерасход денег на внешних сервисах.** Распознавание и языковая модель
оплачиваются по факту; потолка на пользователя нет по тому же решению.
- **Стойкость `ffmpeg` к вредоносному входу.** Разбор чужого формата отдан
+2 -1
View File
@@ -32,7 +32,8 @@ context: |
- docs/architecture.md — устройство; docs/security.md — периметр;
docs/database.md — схема и настройки с числами; docs/adr/ — почему решено
так; docs/research/ — что уже измерено;
- tasks/ROADMAP.md — что приложение уже умеет и чего ещё не умеет.
- openspec/specs/ — что приложение уже умеет; tasks/BACKLOG.md — что осталось
и в каком порядке это берут.
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
первым молча, и заметно это становится в предложении, которое уже написано.
+8 -8
View File
@@ -5,14 +5,14 @@
Конвейер расшифровки: как задача движется по состояниям, что делает воркер,
когда работы нет, и что считается отказом шага.
Описан пока **только пустой прогон воркера** — тот, что нормируют проверки
пакета `internal/controller/worker` и перевод признака в `internal/service`.
Сознательно не описаны переходы состояний и цепочка `created → converted →
transcribe → done | failed`, захват задачи и срок его протухания, отмена
контекста посреди шага, освобождение ресурсов внешних клиентов. Это не значит,
что такого поведения нет: оно живёт в коде, а требования на него не написаны,
потому что требование без проверки — предположение, а не норма. Первая задача,
которая трогает любое из перечисленного, дописывает его сюда.
Описаны пустой прогон воркера, неделимость захвата и срок его протухания, число
попыток и состояние «мертва», нарастающая пауза перед повтором и условие записи
результата держателем захвата. Сознательно не описаны: цепочка переходов
`created → converted → transcribe → done | failed`, отмена контекста посреди
шага и освобождение ресурсов внешних клиентов. Это не значит, что такого
поведения нет: оно живёт в коде, а требования на него не написаны, потому что
требование без проверки — предположение, а не норма. Первая задача, которая
трогает любое из перечисленного, дописывает его сюда.
## Requirements
### Requirement: Пустой прогон воркера — не отказ