## 1. Схема и модель записи - [x] 1.1 Завести шаг схемы под три колонки записи: `original_filename`, `duration_ms`, `size_bytes`. Один шаг на все три; применённый шаг не переписывается. Единица стоит в имени колонки - [x] 1.2 Добавить три поля в `entity.AudioRecord` с комментарием о разрезе «имя файла против заголовка»; неизвестную длительность отличить от нулевой - [x] 1.3 Провести колонки через оба места правки в пакете хранилища: `applyToRecord` кладёт их (это заведение), `applyOwnedByPipeline` — не трогает (конвейер их не меняет), `recordToAudioRecord` читает - [x] 1.4 Добавить темы записи в модель и в чтение записи; в запись их не кладёт ни приём, ни конвейер - [x] 1.5 Прогнать `schema_test.go` и сверку правил `internal/archrules` ## 2. Приём: имя файла, длительность и размер - [x] 2.1 Завести предел длины имени отправителя и уборку управляющих знаков одним местом в домене; предел назвать константой с обоснованием - [x] 2.2 Записать в принимаемую запись очищенное имя, длительность и размер; колонку заголовка оставить пустой - [x] 2.3 Проверить тестом, что имя отправителя не появилось ни в имени файла хранилища, ни в журнале — прежние тесты приватности остаются зелёными ## 3. Отображение доменной ошибки в ответ - [x] 3.1 Завести доменный признак «присланная запись негодна» и обернуть им отказ источника метаданных в службе приёма - [x] 3.2 Написать единую функцию отображения доменной ошибки в код ответа и человекочитаемое сообщение по таблице из `docs/conventions/errors.md` - [x] 3.3 Завести единую форму тела отказа и провести через неё все ветви: ненайденная запись, негодный ввод, отсутствие учётной записи, сбой хранилища - [x] 3.4 Завести доменный признак «запись сверх потолка размера», код `413`, предел в теле числом - [x] 3.5 Убрать опечатку `transcibe` из текста ошибки приёма - [x] 3.6 Завести закрытый перечень машиночитаемых кодов отказа и отдавать их вторым полем тела рядом с сообщением человеку - [x] 3.7 Назвать точку отображения в `docs/architecture.md`, «Единые точки проекта», и снять расхождение в `docs/conventions/errors.md` - [x] 3.8 Дописать в таблицу `docs/conventions/errors.md` ветви, которых там нет: `401`, `403`, `413` и машиночитаемый код отказа — объявленный источник единой точки иначе расходится с ней в первый же день ## 4. Пространство адресов приложения - [x] 4.1 Перевесить группу маршрутов с чужого корня на `/app/`, слой сессии повесить на группу корня, а не на перечень адресов - [x] 4.2 Завести своё правило ограничителя частоты под корень приложения - [x] 4.3 Убрать прежние адреса `POST /api/audio` и `GET /api/status/{id}` целиком ## 5. Адреса чтения - [x] 5.1 `GET /app/me` — идентификатор учётной записи и имя к показу, без адреса почты - [x] 5.2 `GET /app/config` — потолок размера тем же числом, каким сервис ограничивает тело запроса, частота опроса, перечень известных расширений, потолок тем - [x] 5.3 `GET /app/audiorecords` — страница своих записей новыми сверху, с ключом следующей страницы и общим числом; отбор тремя состояниями - [x] 5.4 `GET /app/audiorecords/{id}` — карточка записи без текста, с перечнем доступных видов текста `available_views`; пустой перечень значит «текста ещё нет» - [x] 5.5 `GET /app/audiorecords/{id}/text` — текст названного вида из закрытого перечня `transcript`, `literary`, `replicas`; неизвестный и незаданный вид дают `400`, отсутствующий у записи вид — `409` - [x] 5.6 `POST /app/audiorecords` — ответ списком с местом под признак повторного файла, элемент той же формы, что и карточка ## 6. Отбор и чтение в хранилище - [x] 6.1 Добавить в репозиторий записей выборку по владельцу курсором на паре «время заведения и идентификатор», новыми сверху, с умолчанием и потолком размера страницы - [x] 6.2 Проверить тестом, что выборка списка не читает ни строки текста, ни структуры реплик - [x] 6.3 Разрешить темы записи названиями одним запросом на страницу, а не по запросу на запись - [x] 6.4 Вывести предикаты трёх состояний отбора («в работе», «остановлена», «прошла конвейер») из дескриптора рубежа в `internal/entity`, а не строкой фильтра в репозитории - [x] 6.5 Дописать правило `internal/archrules` на нового потребителя словаря рубежей — отбор списка ## 7. Документация и гейт - [x] 7.1 `docs/database.md` — три новые колонки записи - [x] 7.2 `docs/conventions/web-ui.md` — правило неизвестного пути перечисляет корни сервиса (проверить, что запись уже верна) - [x] 7.3 `CLAUDE.md`, инвариант «Принятая запись не теряется молча» — причина видна владельцу **карточкой записи**, а не опросом готовности: опрос убран, и инвариант ссылается на адрес, которого нет - [x] 7.4 `docs/passport.md` — сценарии потребителей называют `POST /api/audio` и `GET /api/status/:id` действующими; задача `api-tokens` прочитает их как действующие и заведёт токен на несуществующий адрес - [x] 7.5 `docs/security.md` — периметр и таблица поверхностей перечисляют убираемые адреса и не знают ни одного адреса приложения, а изменение добавляет их несколько - [x] 7.6 `docs/architecture.md` — строка компонента «HTTP API» и строка «кто заметит отказ» называют опрос готовности; там же строка про `done` без текста - [x] 7.7 Обновить мутационную проверку `journal_route_test.go`: пути `/api/audio` и `/api/status/...` в ней заменены новыми адресами приложения - [x] 7.8 `task gate` зелёный целиком - [x] 7.9 Поднять сервис локально: шаг схемы применён на чистой базе, адреса приложения подняты, отказ без сессии идёт единой формой, прежние адреса отвечают `404`. Сценарий вошедшего локально не проходится по устройству проекта: сессию выдаёт только провайдер OIDC, а локальный запуск наружу не ходит — этот путь закрыт машиной в тестах, через настоящий роутер ## Критерии приёмки Два источника, и приёмка судится по одному списку. Первый блок — из записи задачи `json-api-for-spa` дословно: файл задачи закрытие удалит, критерии обязаны его пережить. Второй — рубрика ревью дизайна, дополняющая их там, где постановка молчала. ### От постановки - Код ответа отвечает причине отказа, а не месту, где он случился: сбой базы при чтении даёт `500`, а не `404`, негодный файл — `400` с человекочитаемым текстом, а не `500`, а отказ приёма по пустому владельцу — `403`, как и отказ слоя предъявления. Тело ошибки при этом одной формы на всех адресах, без сырого `err.Error()` и без опечатки `transcibe`, а отображение живёт в одной функции, названной в `docs/architecture.md`. Оракул — три теста на код (репозиторий с ошибкой драйвера; файл, который отвергает разбор метаданных; вызов приёма без учётной записи), тест формы на четырёх ветвях отказа, пустой `grep -rn 'transcibe' internal/` и `task gate`, шаг `docs.py check`. - Список отдаётся страницами, новые сверху, и несёт заголовок, имя файла отправителя, длительность, размер, рубеж с признаком остановки и темы, не читая при этом ни расшифровки, ни структуры реплик. Оракул — два теста: выборка больше страницы; запись с расшифровкой, у которой чтение списка не трогает строку текста. - Ответ приёма отдаёт список заведённых записей и место под признак повторного файла, даже когда файл в запросе один, а имя файла отправителя лежит своей колонкой — обрезанное по пределу и без управляющих знаков — при пустом заголовке. Оракул — четыре теста приёма: тело ответа — список из одного элемента с полем признака повтора; имя `разговор.mp3` доходит до `original_filename`; колонка заголовка у принятой записи пуста; имя длиннее предела и с управляющими знаками доходит обрезанным и очищенным. - `GET /app/config` отдаёт потолок размера тем же числом, каким сервер отвергает запись сверх него, а не своей копией. Оракул — тест: значение в ответе равно `entity.MaxRecordSize`, и подмена константы меняет ответ. - Карточка записи и её текст читаются порознь, а прежний опрос готовности отвечает `404`: карточка несёт перечень доступных видов текста, а текст отдаётся названным видом. Оракул — четыре теста: карточка без поля текста, но с перечнем доступных видов; текст вида `transcript`; текст вида `replicas`; прежние адреса `GET /api/status/{id}` и `POST /api/audio` на заведённой записи отвечают `404`. *Правка от 2026-08-15:* критерий приведён к спеке после повторной сверки. Прежняя формулировка требовала признак наличия текста и «тот же вид репликами» — обе воскрешали то, что ревью дизайна уже закрыло: булев признак не выражает состояния «текст есть, реплик нет», а `transcript` репликами не отдаётся, у них своё значение перечня. ### От рубрики ревью дизайна - Страница задаётся непрозрачным ключом на паре «время заведения и идентификатор», и запись, заведённая между двумя страницами, окна не сдвигает. Оракул — два теста: прочитать первую страницу, завести запись, прочитать следующую ключом — ни повторов, ни пропусков; две записи с одинаковым временем заведения приходят в одном и том же порядке при повторном запросе. - Карточка и элемент страницы — одна форма, а элемент ответа приёма равен карточке плюс признак повтора. Оракул — два теста: набор полей карточки совпадает с набором полей элемента страницы, кроме `available_views`; набор полей элемента ответа приёма равен набору полей карточки плюс `duplicate`. - Размер страницы: умолчание применяется, значение сверх потолка усекается, негодное отвергается. Оракул — три теста: запрос без размера; запрос размером сверх потолка; запрос размером ноль даёт `400`. - Отбор различает три состояния, и остановленная запись приходит ровно в одном из них. Оракул — тест на трёх записях: в работе, остановленная, прошедшая конвейер; каждая приходит ровно один раз. - «Текста этого вида ещё нет» отличимо от «записи нет». Оракул — тест: запись с расшифровкой и без структуры на вид `replicas` даёт `409`, а неизвестный идентификатор — `404`. - Отказ по превышению потолка размера проходит через единую форму. Оракул — тест: тело сверх `entity.MaxRecordSize` даёт `413`, а тело ответа несёт код отказа, сообщение и предел числом. - Колонки названы с единицей в имени. Оракул — шаг схемы заводит `duration_ms` и `size_bytes`; тест приёма проверяет, что обе величины доходят до записи. *Правка от 2026-08-15:* требование «неизвестная длительность отличима от нулевой» снято решением владельца после ревью кода. Числовая колонка хранилища пустого значения не держит — проверено прогоном, — а платить за отличимость четвёртой колонкой или текстовым типом не за что: обе величины ставит приём и ставит всегда. - Отсутствие сессии не даёт различить заведённую запись и неизвестную. Оракул — тест: оба запроса без сессии дают `401` с одним телом. - Карточка остановленной записи несёт рубеж, признак остановки и её причину, а машинного текста отказа не несёт. Оракул — тест на остановленной записи.