- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
17 KiB
17 KiB
Критерии приёмки
Дословно из записи задачи record-centric-model. Файл задачи закрытие удалит —
критерии обязаны его пережить.
- Запись, остановленная на шаге, перезапускается снятием признака и продолжает
с того рубежа, где стояла. Оракул — тест: шаг останавливает запись на
normalized, снятиеhalted_atвозвращает её в работу, и следующим идёт отправка на распознавание, а не повторная нормализация. - У прошедшей конвейер записи ссылки на исходник и на opus ведут на разные существующие копии. Оракул — тест полного прохода: обе ссылки заполнены и обе открываются.
- Число воркеров задаётся конфигом, и поведение от него не зависит. Оракул —
прогон теста конвейера при
N=1иN=4: запись доходит доdoneв обоих; приN=0она остаётся вuploadedи не теряется. - Структура реплик строится из сохранённого ответа провайдера без обращения к нему. Оракул — тест на сохранённом вложении: структура собрана, клиент SpeechKit не позван ни разу.
- Запись, застрявшая в рубеже дольше предела, останавливается, а откладывание
опроса предела не сдвигает. Оракул — тест на подставных часах: сотня
откладываний подряд не двигает
state_entered_atи не обнуляет отсчёт, а по истечении предела запись получает признак остановки с причиной «застряла».
Приёмочные критерии из ревью дизайна
Рубрика прохода review-rubric, пункты, не покрытые критериями выше. Приёмка
судится по одному списку.
- Держатель захвата отличим значением, а не занятостью записи. Оракул — тест: шаг A держит запись, признак остановки снимает человек, запись захватывает шаг B; запись результата шагом A не проходит, и отправителю от него ничего не уходит.
- Всякий способ вывести запись из работы сообщает отправителю. Оракул — тест по перечню причин остановки: у каждой отправитель получает сообщение.
- Повтор шага не создаёт второго приложения. Оракул — тест: шаг завершения оборван после записи текста и повторён с прежнего рубежа; строка расшифровки у записи одна.
- Штатная остановка сервиса не тратит отказ. Оракул — тест: шаг прерван отменой контекста, число отказов записи прежнее, признака остановки нет.
- Содержимое записи закрыто во всех коллекциях, где лежит. Оракул — тест: ссылка на сохранённый ответ провайдера без сессии отдаёт отказ, с чужой сессией — тоже.
- Отказ виден с разрезом по шагу. Оракул — тест: отказ шага приведения растит счётчик с меткой своего рубежа.
1. Сущности и рубежи
- 1.1 Завести
entity.AudioRecordс рубежом, временем входа в рубеж, признаком остановки и её причиной, полями очереди и ссылками на приложения - 1.2 Дескриптор рубежа одним объявлением: имя, шаг, чья работа, срок захвата, предел времени, берётся ли в работу. Таблица выбора шага, список отбора захвата и пределы выводятся из него, а не перечисляются порознь
- 1.3 Развести
MoveToStateиPostpone: первый двигает рубеж и время входа в него, второй ставит паузу и снимает захват, рубежа не трогая - 1.4 Заменить
FailиDieнаHalt(причина, текст)иResume();Resumeсбрасывает отказы, паузу и время входа в рубеж, рубеж сохраняет - 1.5 Завести сущности приложения: файл, текст с видом, структура реплик с версией, тема, событие журнала, попытка распознавания
- 1.6 Тест:
Postponeне двигает время входа в рубеж и не сбрасывает отсчёт - 1.7 Тест:
Haltсохраняет рубеж,Resumeвозвращает на него же и заново ставит время входа (критерий 1)
2. Шаг схемы
- 2.1 Новым файлом шага завести коллекции
audio_records,texts,structures,recognitions,record_events,topics - 2.2 Дописать
filesполями формата и длительности - 2.3 Уникальность: тема по паре «владелец и название», текст по паре
«запись и вид» (
texts.kind), структура по паре «запись и версия» - 2.4 Индексы под выборку захвата: рубеж, пауза, срок захвата, признак остановки
- 2.5 Правила доступа новых коллекций: просмотр только владельцем связанной
записи; поле вложения в
recognitionsпомечено защищённым - 2.6 Тот же шаг удаляет прежнюю коллекцию
transcribe_jobs: данных под ней нет, а пустая коллекция висела бы в панели вторым домом для того же понятия - 2.7 Тест шага: на чистом каталоге поднимаются все коллекции новой модели, и принятая следом запись доходит до конечного рубежа
- 2.8 Тест: ссылка на сохранённый ответ без сессии и с чужой сессией даёт отказ (критерий 10)
- 2.9 Обновить
docs/database.mdтем же изменением: гейт сверяет шаг схемы с правкой этого документа
3. Контракты и репозитории
- 3.1
AudioRecognizerотдаёт доменный результат — реплики со временем, плоский текст, байты на хранение — вместо строки; заливка и отправка разделены - 3.2 Контракты репозиториев: запись, файлы, тексты, структура, попытки распознавания, журнал событий, темы
- 3.3 Захват возвращает идентификатор записи и признак этого захвата;
признак уникален для каждого захвата;
acquireColumnsиacquiredRowуходят, срок протухания пишется числом при захвате - 3.4 Запрос захвата: отбор по рубежам из дескриптора, паузе, сроку захвата и отсутствию признака остановки, порядок по времени заведения и ключу
- 3.5 Запись результата условна по признаку этого захвата, а не по занятости; владелец, заголовок, краткое описание и темы шагом не переписываются
- 3.6 Перенацелить сканеры
internal/archrulesс колонок захвата на перечень рубежей: дескриптор против списка отбора против значений шага схемы - 3.7 Тест: захват отдаёт идентификатор и признак, троим одновременным достаётся одному
- 3.8 Тест: остановленная запись захвату не выдаётся
- 3.9 Тест: шаг A теряет захват после снятия остановки человеком и записи не проводит (критерий 6)
4. Адаптер распознавания
- 4.1 Разбор потока
GetRecognitionв реплики со временем внутри адаптера - 4.2 Раздельные заливка в Object Storage и отправка операции; строка попытки заводится до обращения к провайдеру
- 4.3 Заливка проверяет объект нужного размера и не повторяется; отправка не повторяется при заведённом идентификаторе операции
- 4.4 Адаптер отдаёт сырые байты ответа на хранение вложением
- 4.5 Тест: структура собрана из сохранённого вложения, клиент SpeechKit не позван ни разу (критерий 4)
- 4.6 Тест: объект на месте — заливка не повторяется; идентификатор операции на месте — отправка не повторяется
5. Конвейер
- 5.1 Выбор шага по рубежу из дескриптора; шаги перестают быть привязаны к воркеру
- 5.2 Шаг приведения: две отдельные ссылки на файлы вместо одной переставляемой
- 5.3 Шаг отправки: строка попытки распознавания, идентификатор операции и ключ объекта уезжают туда
- 5.4 Шаг опроса зовёт
Postpone, а не переход в то же состояние - 5.5 Шаг завершения: текст строкой
texts, структура строкойstructures, сырой ответ вложением; повтор не заводит второго комплекта - 5.6 Предел времени в рубеже из дескриптора: остановка с причиной «застряла», идентификатор операции при этом сохраняется
- 5.7 Единое место ответа отправителю на всякую остановку, независимо от причины
- 5.8 Отмена по остановке сервиса возвращает число отказов назад и приговора не выносит
- 5.9 Журнал событий пишется на смену рубежа, на остановку и на снятие
остановки; на откладывание — нет; колонка текста отказа зовётся
outcome_text, чтобыerror_textосталось именем одной колонки - 5.10 Тест полного прохода: обе ссылки на файлы заполнены и обе открываются (критерий 2)
- 5.11 Тест: остановка на
normalized, снятие признака, следующим идёт отправка (критерий 1) - 5.12 Тест на подставных часах: сотня откладываний не двигает
state_entered_at, по истечении предела запись останавливается с причиной «застряла» (критерий 5) - 5.13 Тест по перечню причин остановки: у каждой отправитель получает сообщение (критерий 7)
- 5.14 Тест: повтор шага завершения не заводит второй расшифровки (критерий 8)
- 5.15 Тест: отмена контекста не тратит отказ (критерий 9)
6. Воркеры, метрики и настройки
- 6.1 Пул одинаковых воркеров вместо трёх именованных в
main.go - 6.2 Метка счётчика
transcriber_worker_job_count— рубеж, а не имя потока - 6.3 Число воркеров и два предела времени — в
internal/configиconfig.example.tomlс умолчаниями; имена ключей названы в дизайне - 6.4
N = 0поднимает сервис без движения записей - 6.5 Тест конвейера при
N=1иN=4— запись доходит доdone; приN=0остаётся вuploaded(критерий 3) - 6.6 Тест: отказ шага приведения растит счётчик с меткой своего рубежа (критерий 11)
7. Поверхность и панель
- 7.1 Ответ приёма отдаёт рубеж
uploaded; ответ опроса — рубеж из нового перечня плюс полеhalted - 7.2 Текст расшифровки в ответе опроса читается из
textsпо виду «сырая расшифровка», а не по последней записи - 7.3 Правила панели: снятие признака остановки и правка рубежа очищают захват, срок, паузу и отказы и заново ставят время входа в рубеж
- 7.4 Запрет удаления учётной записи считает
audio_recordsиfiles - 7.5 Тесты транспорта под новые значения
statusи полеhalted
8. Документы и гейт
- 8.1 Инвариант
CLAUDE.mdо колонках очереди: перечень колонок съёживается, предмет правила переезжает на перечень рубежей - 8.2 Инвариант
CLAUDE.mdо держателе захвата: держатель отличим значением признака захвата, а не занятостью записи - 8.3
docs/architecture.md: компоненты, цепочка рубежей, единые точки, таблица внешних зависимостей, раздел «Эксплуатация» — чем владелец теперь замечает отказ - 8.4
docs/database.md: коллекции, представление данных, настройки с числовым значением (число воркеров, два предела времени, сроки захвата) - 8.5
docs/review.md: снять ложноположительное «гонка захвата по построению невозможна» — построение снято пулом одинаковых воркеров - 8.6 Разделы
Purposeспекpipelineиstorageпри архивации: цепочка рубежей перестала быть «сознательно не описанной», а строка про непереносимые прежние данные — верной - 8.7
task gateзелёный целиком - 8.8 Поведенческая проверка живым запуском: подъём с
telegram.enabled = falseи подставным распознавателем, запись доходит доdone