## MODIFIED Requirements ### Requirement: Таймаут ожидания результата Фоновый вахтёр SHALL с периодом 30 с переводить задания `processing` и `published` с `reply_deadline < now()` в `status = timeout`, `error_code = TIMEOUT`, `finished_at = now()`, а их файлы (по `current_recognition_job_id`) — в `stored_file.status = ERROR`. Публикатор при переводе задания в `dlq` SHALL так же переводить файл в `ERROR`. Ручной повтор после TIMEOUT/NO_ACK SHALL создавать новое задание по требованию «Новая попытка обработки файла». #### Scenario: Сервис взял, но не ответил - **WHEN** задание в `processing` и `reply_deadline` прошёл без результата - **THEN** не позднее чем через 30 с задание имеет `status = timeout`, файл — `ERROR` с `error_code = TIMEOUT` ## ADDED Requirements ### Requirement: Новая попытка обработки файла Ручной повтор SHALL создать recognition_job с новым id, attempt = прежний attempt + 1, тем же file_id, pipeline и max_attempts, статусом queued. Предыдущая запись SHALL сохраняться без изменений. Файл SHALL ссылаться на новое задание и получить UPLOADED. Ссылка S3 на исходник и consolidation_id сохраняются; новых документов, файлов и копий исходника SHALL NOT создаваться. #### Scenario: Повтор после NO_ACK - **WHEN** tabular-задание ОСВ РБ завершилось dlq/NO_ACK с attempt = 1 - **THEN** новое задание queued имеет attempt = 2, файл UPLOADED с прежним id и объектом S3, current_recognition_job_id указывает на новый id ### Requirement: Изоляция новой попытки Новое задание SHALL начинаться без ack, результата, ошибок, метрик и времён старого задания, со свежим requested_at. Публикатор SHALL вычислить новый reply_deadline и Nats-Msg-Id для нового job_id/attempt. Запоздавшие ack/результаты прежней финальной попытки MUST NOT менять файл, создавать документы или мешать обработке нового задания. #### Scenario: Старый ответ - **WHEN** после создания попытки 2 пришли ack и done для попытки 1 в timeout - **THEN** текущее задание, статус файла и документы не меняются; ответ логируется и игнорируется #### Scenario: Публикация после недоступности NATS - **WHEN** ручной повтор принят, а NATS временно недоступен - **THEN** задание остаётся queued; после восстановления публикуется со свежим сроком и новым Nats-Msg-Id ### Requirement: Конкурентные запросы повтора Проверка текущего задания и лимита, создание новой попытки и переключение файла SHALL быть атомарны. Два запроса к одной неуспешной попытке MUST NOT создавать два новых задания. При rollback новое задание, переключение файла и аудит SHALL откатываться вместе. #### Scenario: Двойное нажатие - **WHEN** два параллельных запроса повторяют attempt = 1 - **THEN** создан ровно один job attempt = 2; второй одиночный запрос получает 409 FILE_NOT_RETRYABLE, а пакетный отмечает файл skipped; attempt = 3 не создаётся