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