# ✨ Свести приём и чтение записей к одному контракту для приложения - **Тип:** feature - **Категория:** Очередь — Единая точка трансляции доменной ошибки — база и для экранов, и для токенов; список своих записей заводится после владельца, а не до. - **Зачем:** Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем. Экраны заводят задачу, видят её состояние и листают список — всё через один контракт. Обработчик `GET /api/status/:id` сегодня отвечает `404` на **любую** ошибку чтения, включая сбой базы, а `POST /api/audio` — `500` на любую ошибку заведения, включая негодный файл. Экран, построенный на таком контракте, показывает «не найдено» при упавшей базе. Отдельная ветка того же класса найдена ревью задачи `remove-telegram-intake` 2026-08-15, проход сверки требований: приём отвергает пустого владельца своей ошибкой, а транспорт переводит **любую** ошибку службы в `500`. Спека приёма при этом держит эту проверку ради понятного отказа — «отвечает отправителю понятным отказом до того, как запись попадёт в память». Сегодня путь недостижим: слой предъявления отвергает такого вызывающего раньше. Он становится достижимым вместе с личными токенами и экранами приложения, то есть ровно здесь. ## Затрагивает - `POST /api/audio` и `GET /api/status/:id` — коды ответа и форма ошибки; - новый эндпоинт списка своих записей с постраничным чтением; - единая точка отображения доменной ошибки в код ответа — её сегодня нет ([conventions/errors.md](../../docs/conventions/errors.md)); - `internal/contract` — доменные ошибки под отображение; - `internal/controller/http` целиком; - `docs/architecture.md`, раздел «Единые точки проекта». ## Критерии приёмки - Код ответа отвечает причине отказа, а не месту, где он случился: сбой базы при чтении даёт `500`, а не `404`, негодный файл — `400` с человекочитаемым текстом, а не `500`, а отказ приёма по пустому владельцу — `403`, как и отказ слоя предъявления. Оракул — три теста: репозиторий, возвращающий ошибку драйвера; файл, который отвергает разбор метаданных; вызов приёма без учётной записи. - Тело ошибки одной формы на всех эндпоинтах, не содержит сырого `err.Error()` и опечатки `transcibe`. Оракул — тест на четырёх ветвях отказа (форма совпадает, текста внутренней ошибки в теле нет) плюс пустой `grep -rn 'transcibe' internal/`. - Список записей отдаётся страницами и упорядочен по времени создания. Оракул — тест на выборке больше страницы. - Отображение ошибки живёт в одной функции, и она названа в `docs/architecture.md`. Оракул — `task gate`, шаг `docs.py check`. - Ответ приёма отдаёт список заведённых записей и место под признак повторного файла, даже когда файл в запросе один. Оракул — тест приёма: тело ответа — список из одного элемента, у элемента есть поле признака повтора. ## Рамки Аутентификацию и владельца не заводим — это `oidc-login` и `record-ownership`; задача про форму контракта. Публичный контракт после мерджа обратной правкой не откатывается, и по решению владельца от 2026-08-12 форма ответа приёма согласуется здесь один раз — сразу списком и с местом под признак повтора, — чтобы `dedup-by-content-hash`, `multi-file-upload` и `reject-oversized-recording` её не переписывали, а экран загрузки не переделывался под вторую форму. Приём по-прежнему берёт из запроса один файл: меняется форма ответа, не число файлов. Сюда же приехала опечатка `transcibe` в тексте ошибки приёма (нашёл проход `review-specs` на ревью change `2026-08-11-fix-http-handler-tests`): в одиночку текст ошибки менять нельзя, а здесь контракт переписывается целиком.