# Критерии приёмки

Дословно из записи задачи `record-centric-model`. Файл задачи закрытие удалит —
критерии обязаны его пережить.

1. Запись, остановленная на шаге, перезапускается снятием признака и продолжает
   с того рубежа, где стояла. Оракул — тест: шаг останавливает запись на
   `normalized`, снятие `halted_at` возвращает её в работу, и следующим идёт
   отправка на распознавание, а не повторная нормализация.
2. У прошедшей конвейер записи ссылки на исходник и на opus ведут на разные
   существующие копии. Оракул — тест полного прохода: обе ссылки заполнены и обе
   открываются.
3. Число воркеров задаётся конфигом, и поведение от него не зависит. Оракул —
   прогон теста конвейера при `N=1` и `N=4`: запись доходит до `done` в обоих;
   при `N=0` она остаётся в `uploaded` и не теряется.
4. Структура реплик строится из сохранённого ответа провайдера без обращения к
   нему. Оракул — тест на сохранённом вложении: структура собрана, клиент
   SpeechKit не позван ни разу.
5. Запись, застрявшая в рубеже дольше предела, останавливается, а откладывание
   опроса предела не сдвигает. Оракул — тест на подставных часах: сотня
   откладываний подряд не двигает `state_entered_at` и не обнуляет отсчёт, а по
   истечении предела запись получает признак остановки с причиной «застряла».

## Приёмочные критерии из ревью дизайна

Рубрика прохода `review-rubric`, пункты, не покрытые критериями выше. Приёмка
судится по одному списку.

6. Держатель захвата отличим **значением**, а не занятостью записи. Оракул —
   тест: шаг A держит запись, признак остановки снимает человек, запись
   захватывает шаг B; запись результата шагом A не проходит, и отправителю от
   него ничего не уходит.
7. Всякий способ вывести запись из работы сообщает отправителю. Оракул — тест по
   перечню причин остановки: у каждой отправитель получает сообщение.
8. Повтор шага не создаёт второго приложения. Оракул — тест: шаг завершения
   оборван после записи текста и повторён с прежнего рубежа; строка расшифровки
   у записи одна.
9. Штатная остановка сервиса не тратит отказ. Оракул — тест: шаг прерван
   отменой контекста, число отказов записи прежнее, признака остановки нет.
10. Содержимое записи закрыто во всех коллекциях, где лежит. Оракул — тест:
    ссылка на сохранённый ответ провайдера без сессии отдаёт отказ, с чужой
    сессией — тоже.
11. Отказ виден с разрезом по шагу. Оракул — тест: отказ шага приведения растит
    счётчик с меткой своего рубежа.

## 1. Сущности и рубежи

- [x] 1.1 Завести `entity.AudioRecord` с рубежом, временем входа в рубеж,
      признаком остановки и её причиной, полями очереди и ссылками на приложения
- [x] 1.2 Дескриптор рубежа одним объявлением: имя, шаг, чья работа, срок захвата,
      предел времени, берётся ли в работу. Таблица выбора шага, список отбора
      захвата и пределы **выводятся** из него, а не перечисляются порознь
- [x] 1.3 Развести `MoveToState` и `Postpone`: первый двигает рубеж и время входа
      в него, второй ставит паузу и снимает захват, рубежа не трогая
- [x] 1.4 Заменить `Fail` и `Die` на `Halt(причина, текст)` и `Resume()`;
      `Resume` сбрасывает отказы, паузу **и время входа в рубеж**, рубеж сохраняет
- [x] 1.5 Завести сущности приложения: файл, текст с видом, структура реплик с
      версией, тема, событие журнала, попытка распознавания
- [x] 1.6 Тест: `Postpone` не двигает время входа в рубеж и не сбрасывает отсчёт
- [x] 1.7 Тест: `Halt` сохраняет рубеж, `Resume` возвращает на него же и заново
      ставит время входа **(критерий 1)**

## 2. Шаг схемы

- [x] 2.1 Новым файлом шага завести коллекции `audio_records`, `texts`,
      `structures`, `recognitions`, `record_events`, `topics`
- [x] 2.2 Дописать `files` полями формата и длительности
- [x] 2.3 Уникальность: тема по паре «владелец и название», текст по паре
      «запись и вид» (`texts.kind`), структура по паре «запись и версия»
- [x] 2.4 Индексы под выборку захвата: рубеж, пауза, срок захвата, признак
      остановки
- [x] 2.5 Правила доступа новых коллекций: просмотр только владельцем связанной
      записи; поле вложения в `recognitions` помечено защищённым
- [x] 2.6 Тот же шаг удаляет прежнюю коллекцию `transcribe_jobs`: данных под ней
      нет, а пустая коллекция висела бы в панели вторым домом для того же понятия
- [x] 2.7 Тест шага: на чистом каталоге поднимаются все коллекции новой модели, и
      принятая следом запись доходит до конечного рубежа
- [x] 2.8 Тест: ссылка на сохранённый ответ без сессии и с чужой сессией даёт
      отказ **(критерий 10)**
- [x] 2.9 Обновить `docs/database.md` тем же изменением: гейт сверяет шаг схемы
      с правкой этого документа

## 3. Контракты и репозитории

- [x] 3.1 `AudioRecognizer` отдаёт доменный результат — реплики со временем,
      плоский текст, байты на хранение — вместо строки; заливка и отправка
      разделены
- [x] 3.2 Контракты репозиториев: запись, файлы, тексты, структура, попытки
      распознавания, журнал событий, темы
- [x] 3.3 Захват возвращает **идентификатор записи и признак этого захвата**;
      признак уникален для каждого захвата; `acquireColumns` и `acquiredRow`
      уходят, срок протухания пишется числом при захвате
- [x] 3.4 Запрос захвата: отбор по рубежам из дескриптора, паузе, сроку захвата и
      отсутствию признака остановки, порядок по времени заведения и ключу
- [x] 3.5 Запись результата условна по признаку **этого** захвата, а не по
      занятости; владелец, заголовок, краткое описание и темы шагом не
      переписываются
- [x] 3.6 Перенацелить сканеры `internal/archrules` с колонок захвата на перечень
      рубежей: дескриптор против списка отбора против значений шага схемы
- [x] 3.7 Тест: захват отдаёт идентификатор и признак, троим одновременным
      достаётся одному
- [x] 3.8 Тест: остановленная запись захвату не выдаётся
- [x] 3.9 Тест: шаг A теряет захват после снятия остановки человеком и записи не
      проводит **(критерий 6)**

## 4. Адаптер распознавания

- [x] 4.1 Разбор потока `GetRecognition` в реплики со временем внутри адаптера
- [x] 4.2 Раздельные заливка в Object Storage и отправка операции; строка попытки
      заводится до обращения к провайдеру
- [x] 4.3 Заливка проверяет объект нужного размера и не повторяется; отправка не
      повторяется при заведённом идентификаторе операции
- [x] 4.4 Адаптер отдаёт сырые байты ответа на хранение вложением
- [x] 4.5 Тест: структура собрана из сохранённого вложения, клиент SpeechKit не
      позван ни разу **(критерий 4)**
- [x] 4.6 Тест: объект на месте — заливка не повторяется; идентификатор операции
      на месте — отправка не повторяется

## 5. Конвейер

- [x] 5.1 Выбор шага по рубежу из дескриптора; шаги перестают быть привязаны к
      воркеру
- [x] 5.2 Шаг приведения: две отдельные ссылки на файлы вместо одной
      переставляемой
- [x] 5.3 Шаг отправки: строка попытки распознавания, идентификатор операции и
      ключ объекта уезжают туда
- [x] 5.4 Шаг опроса зовёт `Postpone`, а не переход в то же состояние
- [x] 5.5 Шаг завершения: текст строкой `texts`, структура строкой `structures`,
      сырой ответ вложением; повтор не заводит второго комплекта
- [x] 5.6 Предел времени в рубеже из дескриптора: остановка с причиной «застряла»,
      идентификатор операции при этом сохраняется
- [x] 5.7 Единое место ответа отправителю на всякую остановку, независимо от
      причины
- [x] 5.8 Отмена по остановке сервиса возвращает число отказов назад и приговора
      не выносит
- [x] 5.9 Журнал событий пишется на смену рубежа, на остановку и на снятие
      остановки; на откладывание — нет; колонка текста отказа зовётся
      `outcome_text`, чтобы `error_text` осталось именем одной колонки
- [x] 5.10 Тест полного прохода: обе ссылки на файлы заполнены и обе открываются
      **(критерий 2)**
- [x] 5.11 Тест: остановка на `normalized`, снятие признака, следующим идёт
      отправка **(критерий 1)**
- [x] 5.12 Тест на подставных часах: сотня откладываний не двигает
      `state_entered_at`, по истечении предела запись останавливается с причиной
      «застряла» **(критерий 5)**
- [x] 5.13 Тест по перечню причин остановки: у каждой отправитель получает
      сообщение **(критерий 7)**
- [x] 5.14 Тест: повтор шага завершения не заводит второй расшифровки
      **(критерий 8)**
- [x] 5.15 Тест: отмена контекста не тратит отказ **(критерий 9)**

## 6. Воркеры, метрики и настройки

- [x] 6.1 Пул одинаковых воркеров вместо трёх именованных в `main.go`
- [x] 6.2 Метка счётчика `transcriber_worker_job_count` — рубеж, а не имя потока
- [x] 6.3 Число воркеров и два предела времени — в `internal/config` и
      `config.example.toml` с умолчаниями; имена ключей названы в дизайне
- [x] 6.4 `N = 0` поднимает сервис без движения записей
- [x] 6.5 Тест конвейера при `N=1` и `N=4` — запись доходит до `done`; при `N=0`
      остаётся в `uploaded` **(критерий 3)**
- [x] 6.6 Тест: отказ шага приведения растит счётчик с меткой своего рубежа
      **(критерий 11)**

## 7. Поверхность и панель

- [x] 7.1 Ответ приёма отдаёт рубеж `uploaded`; ответ опроса — рубеж из нового
      перечня плюс поле `halted`
- [x] 7.2 Текст расшифровки в ответе опроса читается из `texts` по **виду**
      «сырая расшифровка», а не по последней записи
- [x] 7.3 Правила панели: снятие признака остановки и правка рубежа очищают
      захват, срок, паузу и отказы и заново ставят время входа в рубеж
- [x] 7.4 Запрет удаления учётной записи считает `audio_records` и `files`
- [x] 7.5 Тесты транспорта под новые значения `status` и поле `halted`

## 8. Документы и гейт

- [x] 8.1 Инвариант `CLAUDE.md` о колонках очереди: перечень колонок съёживается,
      предмет правила переезжает на перечень рубежей
- [x] 8.2 Инвариант `CLAUDE.md` о держателе захвата: держатель отличим значением
      признака захвата, а не занятостью записи
- [x] 8.3 `docs/architecture.md`: компоненты, цепочка рубежей, единые точки,
      таблица внешних зависимостей, раздел «Эксплуатация» — чем владелец теперь
      замечает отказ
- [x] 8.4 `docs/database.md`: коллекции, представление данных, настройки с
      числовым значением (число воркеров, два предела времени, сроки захвата)
- [x] 8.5 `docs/review.md`: снять ложноположительное «гонка захвата по построению
      невозможна» — построение снято пулом одинаковых воркеров
- [x] 8.6 Разделы `Purpose` спек `pipeline` и `storage` при архивации: цепочка
      рубежей перестала быть «сознательно не описанной», а строка про непереносимые
      прежние данные — верной
- [x] 8.7 `task gate` зелёный целиком
- [x] 8.8 Поведенческая проверка живым запуском: подъём с `telegram.enabled =
      false` и подставным распознавателем, запись доходит до `done`
