- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
9.6 KiB
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в плане стройки стоит ниже, а по «Рамкам» постановки предшествует — без предела по времени у шага срок захвата не может его превысить.