## MODIFIED Requirements

### Requirement: Захват задачи неделим

Захват записи воркером SHALL быть одним неделимым запросом к базе: выбор
подходящей записи и пометка её захваченной MUST происходить вместе, одним
оператором с возвратом.

Захват MUST возвращать **идентификатор записи и признак этого захвата**, а не
перечень её колонок. Колонки записи шаг читает сам, обычным чтением. Иначе
всякая новая колонка аудиозаписи попадала бы под инвариант проекта о колонках
очереди, и забытая в захвате колонка приезжала бы нулевой, а первое же
сохранение писало бы этот ноль поверх сохранённого значения.

**Признак захвата MUST быть значением, уникальным для каждого захвата**, а не
признаком занятости. Условие записи результата сверяет именно это значение:
захват, перевыданный другому — по протуханию срока или после того, как человек
снял признак остановки подкомандой оснастки, — обязан обращать запись первого в
отказ.
Условие, проверяющее лишь непустоту признака или срок, пропустило бы обоих, и
два шага записали бы в одну запись по очереди, испортив её результат.

Одна и та же запись MUST доставаться ровно одному захватившему. Двум вызывающим,
пришедшим за работой одновременно, запись MUST достаться одному, а второй MUST
получить признак «работы сейчас нет».

Срок протухания захвата MUST ехать с рубежом записи, а не с воркером: воркер не
привязан к шагу и не знает заранее, что вытянет. Срок MUST записываться числом
при самом захвате.

Порядок выборки MUST быть определён однозначно: сравнения по неуникальному
значению для этого мало, и к нему MUST добавляться ключ записи. Иначе порядок
обработки невоспроизводим, а проверка, опирающаяся на «следующую» запись, зелена
через раз.

Требование стоит на инварианте проекта «Принятая запись не теряется молча»:
захват, разделённый на два шага, отдаёт одну запись двум воркерам, и работа
одного из них теряется без следа.

Признак «работы нет» этим требованием не переопределяется — его нормирует
требование «Пустой прогон воркера — не отказ».

#### Scenario: За работой пришли трое разом

- **GIVEN** к работе пригодна ровно одна запись
- **WHEN** три захвата идут одновременно
- **THEN** запись получает ровно один из них
- **AND** двое остальных получают признак «работы сейчас нет»

#### Scenario: Захваченная запись не выдаётся второй раз

- **GIVEN** запись захвачена и срок захвата не истёк
- **WHEN** приходит следующий захват
- **THEN** эта запись ему не выдаётся

#### Scenario: Захват отдаёт идентификатор и свой признак

- **GIVEN** к работе пригодна запись
- **WHEN** воркер её захватывает
- **THEN** захват возвращает идентификатор записи и признак этого захвата
- **AND** колонки записи шаг читает отдельным чтением

#### Scenario: Признак перевыданного захвата отличается от прежнего

- **GIVEN** запись захвачена, и признак первого захвата известен
- **WHEN** человек снимает признак остановки, и запись захватывает другой воркер
- **THEN** признак нового захвата отличается от признака первого

### Requirement: Результат пишет только держатель захвата

Шаг конвейера SHALL записывать свой результат только тогда, когда захват записи
всё ещё принадлежит ему. Запись MUST быть условна по **признаку этого захвата** —
значению, уникальному для каждого захвата, — а не по занятости записи вообще.
Шаг, чей захват за время работы достался другому, MUST завершиться без записи
результата.

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

Довод про два ответа отправителю из требования ушёл вместе с доставкой: обращений
наружу шаг не делает. Требование от этого не ослабло — порча записи двумя
пишущими остаётся его предметом целиком.

Шаг MUST записывать только те поля, которыми распоряжается сам. Запись он держит
снимком с момента захвата и до записи — это часы, — и безусловная запись снимка
стёрла бы всё, что владелец правил за это время: молча, без строки в журнале и
без отказа тому, кто правил. Владелец записи, заголовок, краткое описание и темы
конвейер MUST не трогать.

Возвращает остановленную запись в работу сегодня владелец сервиса подкомандой
оснастки — своего экрана для этого у сервиса нет. Норму это не меняет: она
написана про поля, а не про того, чьей рукой правка сделана, и переживёт
появление экранов.

#### Scenario: Правка владельца пережила сохранение шага

- **GIVEN** шаг держит захваченную запись
- **AND** владелец за это время изменил поле, которого шаг не касается
- **WHEN** шаг записывает свой результат
- **THEN** результат шага записан
- **AND** правка владельца на месте

#### Scenario: Захват ушёл под работающим шагом

- **GIVEN** шаг работает над захваченной записью
- **AND** за это время та же запись досталась другому захвату
- **WHEN** первый шаг доходит до записи результата
- **THEN** результат не записывается

#### Scenario: Человек снял остановку под работающим шагом

- **GIVEN** шаг работает над захваченной записью
- **AND** человек за это время снял с неё признак остановки, освободив захват
- **AND** запись досталась другому воркеру
- **WHEN** первый шаг доходит до записи результата
- **THEN** результат не записывается

### Requirement: Остановка записи — признак, а не рубеж

Сервис SHALL останавливать запись отдельным признаком с причиной и MUST не
стирать при этом достигнутый рубеж. Признак MUST нести время остановки, причину
и машинный текст отказа.

Снятие признака SHALL возвращать запись в работу **с того рубежа, где она
стояла**, и MUST сбрасывать **всё, чем прошлый прогон её удерживал**:

- признак остановки — время остановки, причину и машинный текст отказа;
- признак захвата и срок его протухания;
- число отказов;
- паузу перед повтором;
- время входа в рубеж.

Перечень назван целиком и в одном месте, потому что забытое поле не даёт ни
отказа, ни строки в журнале. Оставленный признак захвата держит запись занятой до
протухания срока и отдаёт её потом чужому шагу — тому, чей результат условен по
прежнему значению. Оставленное время входа в рубеж останавливает запись снова
первым же захватом, если остановленной она простояла дольше предела, и перезапуск
не работает вовсе. Оставленные отказы и пауза откладывают первый же прогон на
накопленный срок.

Возврат в работу MUST идти **через домен**: тот, кто его делает, называет запись,
а поля выше сбрасывает домен одним действием. Правка колонок мимо домена
повторяет перечень вторым местом, и второе место расходится с первым молча.

Возврат в работу MUST писать событие журнала записи с происхождением «человек».
Иначе запись, побывавшая остановленной и вернувшаяся в работу, неотличима в
журнале от записи, которую конвейер вёл без остановок, а происхождение события
перестаёт различать что-либо: другого писателя, кроме конвейера, у журнала не
остаётся.

Инструментом возврата сегодня служит подкоманда набора инструментов
разработчика: панели у сервиса нет, а экраны владельца приносят отдельные задачи.
Норма написана про поля и про домен, а не про инструмент, и появление экрана её
не трогает.

Прежние состояния отказа и смерти MUST не заводиться заново: обе причины
восстанавливаются одинаково — снятием признака, — и различие между ними
перестаёт быть структурным, оставаясь причиной остановки. Состояние, называющее
отказ, стирает достигнутый рубеж, и продолжение с места остановки становится
невозможным.

**Способ вывести запись из выборки MUST быть один — этот признак.** Второго
признака, исключающего запись из работы помимо рубежа и паузы, MUST не
заводиться: два способа расходятся, и молчаливо теряется тот, который забыли
проверить. Условие отбора MUST не выводить запись из выборки молча — запись,
переставшая браться в работу, обязана нести признак остановки с причиной.

Остановку MUST ставить тот, кто запись захватил. Перевод принадлежит одному
месту: условие отбора, молча пропускающее запись мимо выборки, оставило бы её
без следа.

Остановленная запись MUST не выдаваться захвату.

#### Scenario: Остановленная запись продолжает с места остановки

- **GIVEN** шаг остановил запись на рубеже приведения
- **WHEN** признак остановки снимают
- **THEN** следующим идёт отправка на распознавание, а не повторное приведение

#### Scenario: Остановленная запись не выдаётся захвату

- **GIVEN** у записи стоит признак остановки
- **WHEN** за её рубежом приходит захват
- **THEN** запись ему не выдаётся

#### Scenario: Снятие признака сбрасывает всех сторожей

- **GIVEN** запись остановлена с накопленными отказами и паузой
- **AND** остановленной она простояла дольше предела времени в рубеже
- **WHEN** признак остановки снимают
- **THEN** число отказов, пауза и время входа в рубеж сброшены
- **AND** ближайший захват выдаёт запись, а не останавливает её снова

#### Scenario: Возврат в работу освобождает захват

- **GIVEN** запись остановлена, и признак прошлого захвата на ней стоит
- **WHEN** признак остановки снимают
- **THEN** признака захвата и срока его протухания на записи нет
- **AND** ближайший захват выдаёт запись, не дожидаясь протухания прежнего срока

#### Scenario: Возврат в работу виден в журнале записи

- **GIVEN** запись остановлена
- **WHEN** её возвращают в работу
- **THEN** в журнале событий записи есть событие с происхождением «человек»

### Requirement: Конвейер ответа отправителю не шлёт

Шаг конвейера SHALL доводить запись до достигнутого рубежа и MUST не обращаться
к отправителю вовсе — ни с готовым текстом, ни с сообщением о неудаче. Исход
своей записи владелец узнаёт **карточкой записи**; адрес карточки и содержимое
ответа нормирует capability `archive`.

Владелец сервиса узнаёт исход журналом и журналом событий записи. Панели, где он
видел бы то же строкой таблицы, у сервиса нет — она ушла вместе со встроенным
хранилищем, и второго канала наблюдения это не отняло: журнал событий пишется
по-прежнему, а читается запросом к базе, пока экрана нет.

Держатель нормы сменился вместе с убранным опросом готовности: прежде исход
отдавал адрес опроса, нормированный capability `intake`, и адреса этого больше
нет. Обязанность при этом не изменилась — изменилось только то, каким адресом
она исполняется.

Требование заведено взамен доставки в чат, убранной вместе с входом Telegram.
Без него молчание конвейера читалось бы как недоделка: прежде ответ уходил, и
всякий, кто помнит это, ищет в шаге отправку, а её отсутствие принимает за
потерянную ветку.

Инвариант проекта «Принятая запись не теряется молча» держится теперь карточкой
записи — там остановка видна признаком и причиной — и журналом владельца, где у
неё стоит причина. Обязанность при этом сменила направление: прежде об отказе
сообщали, теперь отказ доступен спросившему. Отправитель, который не
спрашивает, об остановке не узнаёт.

Записи, которой этот канал недоступен, не бывает: у каждой записи есть владелец,
и карточка отдаёт ему её исход. Держится это обязательностью владельца в схеме —
норму держит capability `storage`.

#### Scenario: Готовый текст отправителю не уходит

- **GIVEN** запись дошла до конечного рубежа
- **WHEN** шаг конвейера её завершает
- **THEN** ни одного обращения наружу с текстом расшифровки не уходит
- **AND** текст достаётся отдельным адресом текста записи

#### Scenario: Остановка видна карточкой, а не сообщением

- **GIVEN** запись остановлена по исчерпании отказов
- **WHEN** владелец записи спрашивает её карточку
- **THEN** ответ несёт достигнутый рубеж, признак остановки и её причину
- **AND** в журнале владельца сервиса есть запись об остановке с причиной
