Files
transcriber/openspec/changes/archive/2026-08-14-record-centric-model/proposal.md
T
av 1576d06735 внутренняя модель перестроена вокруг аудиозаписи
- audiorecords вместо transcribe_jobs: приложения (texts, structures,
  recognitions, record_events, topics) живут своими коллекциями, ссылки на
  исходник и на приведённую копию перестали переставляться
- рубеж называет достигнутое, отказ стал признаком остановки с причиной, а
  сторожей стало двое: число отказов и время в рубеже
- воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг
  выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
2026-08-14 20:20:33 +03:00

9.6 KiB

Why

Сервис объявлен архивом: записи и расшифровки лежат бессрочно, к ним возвращаются через месяцы, а поверх них строятся список, темы, уровни текста и учёт расхода. Держать всё это негде — центральной сущности «аудиозапись» в сервисе нет вовсе: есть задача конвейера, у которой поля захвата лежат в одной строке с расшифровкой, указатель на файл переставляет каждый шаг, а отказ стирает достигнутый рубеж и делает продолжение с места остановки невозможным.

Всякая задача, взятая раньше этой, будет переписана вместе с моделью — потому владелец 2026-08-14 поставил её первой в план стройки.

What Changes

  • Центральная сущность — аудиозапись. Домен записи (владелец, заголовок, краткое описание, рубеж, ссылки на приложения) отделяется от того, что нужно только конвейеру, и от того, что принадлежит провайдеру распознавания.
  • Приложения к записи живут отдельными строками. Файлы, тексты, структура реплик, темы, журнал событий и попытка распознавания перестают быть колонками одной строки и адресуются ссылками с записи.
  • Ссылки на файлы перестают переставляться. У записи две отдельные ссылки — на исходник и на приведённую копию, — и обе живут до конца. Сегодня их одна, и прошедшая конвейер запись ведёт на объект во внешнем хранилище: послушать загруженное нечем.
  • Копия во внешнем хранилище перестаёт быть файлом записи. Она существует только потому, что распознаватель читает аудио по адресу, и переезжает в строку о попытке распознавания вместе с идентификатором операции.
  • Сырой ответ распознавателя сохраняется целиком — вложением, а не колонкой. Результат операции у провайдера не переспрашивается, а связь реплики с говорящим сервис строить пока не умеет: когда научится, архив пересчитается из сохранённого без единого рубля.
  • Состояние называет достигнутое, а не предстоящее. Цепочка рубежей: uploaded → normalized → submitted → transcribed → done. BREAKING: перечень состояний в ответе о записи меняется целиком — публичный контракт HTTP API объявлен проектом необратимым.
  • Остановка становится признаком, а не состоянием. Прежние failed и dead схлопываются в признак остановки с причиной; достигнутый рубеж при этом сохраняется, и снятие признака продолжает работу с места остановки, а не с начала. Снимает признак владелец панели — поверхности для владельца записи у сервиса пока нет, её заводит задача с экранами.
  • Сторожей становится двое. Число отказов ограничивает повторы внутри шага, время в рубеже — застревание. Сегодня обе роли навешаны на счётчик попыток, и он не справляется ни с одной: операция, зависшая у провайдера, опрашивается вечно.
  • Предел времени в рубеже — два числа: час на свою работу, сутки на чужую. Достигнут предел — запись останавливается с причиной «застряла».
  • Переход и откладывание разводятся. Шаг опроса перестаёт изображать переход в то же самое состояние: откладывание ставит паузу и снимает захват, а рубежа и времени входа в него не трогает.
  • Шаг с внешней оплатой проверяет сделанное прежде, чем платить второй раз.
  • Воркеры теряют специализацию, а их число задаётся настройкой; ноль — законное значение: записи принимаются и не двигаются.
  • Переноса данных нет. Сервис на сервере остановлен, прежние записи удалены решением владельца 2026-08-14, и новая модель заводится с чистого листа.

Capabilities

New Capabilities

  • recognition: попытка распознавания у внешнего провайдера — что о ней хранится, почему сырой ответ сохраняется целиком, как из сохранённого строится структура реплик и почему разбор формата провайдера не доходит до конвейера.

Modified Capabilities

  • pipeline: цепочка рубежей и смысл состояния; остановка признаком вместо состояний отказа и смерти; два сторожа вместо одного; предел времени в рубеже; разведение перехода и откладывания; необязательный шаг, чей отказ не роняет запись; захват, возвращающий один идентификатор; воркер без специализации и его число настройкой.
  • storage: аудиозапись центральной сущностью и её приложения отдельными коллекциями; две отдельные ссылки на файлы вместо одной переставляемой; словарь тем на каждого владельца; журнал событий записи; перенос живых записей шагом схемы.
  • intake: перечень состояний в ответе о приёме и об опросе готовности.

Impact

  • Схема хранилища: новый шаг — коллекции audio_records, texts, structures, recognitions, record_events, topics; прежняя transcribe_jobs уходит; правка files. Применённые шаги не переписываются.
  • Публичный контракт HTTP API: значения поля состояния. Необратимо.
  • internal/entity — сущность записи, перечень рубежей, переходы, откладывание, остановка признаком.
  • internal/contract — распознаватель отдаёт доменный результат вместо строки, заливка и отправка разделены; контракты репозиториев записи, файлов, текстов, структуры, попыток распознавания и журнала.
  • internal/adapter/recognizer/yandex — разбор потока результата в реплики, раздельные заливка и отправка, отдача сырых байтов на хранение.
  • internal/adapter/repo/pocketbase — запрос захвата, отображение записи, правила панели.
  • internal/service — шаги, таблица выбора следующего шага по рубежу, остановка признаком.
  • internal/controller/worker и main.go — пул вместо трёх именованных воркеров.
  • internal/config и config.example.toml — число воркеров, срок захвата по шагу, два предела времени в рубеже.
  • docs/architecture.md, docs/database.md, инварианты CLAUDE.md о колонках очереди и о держателе захвата.
  • Предшествующая задача: external-call-timeouts в плане стройки стоит ниже, а по «Рамкам» постановки предшествует — без предела по времени у шага срок захвата не может его превысить.