5.3 KiB
Spec Delta
Purpose
Дельта change quality-metrics (метрики качества первички).
MODIFIED Requirements
Requirement: Inbox принимает результат идемпотентно
Для конверта со status ∈ {done, needs_check, error} ядро SHALL валидировать его по
recognition-result-message.schema.json, найти задание по job_id и: если result_msg_id задания уже равен
Nats-Msg-Id сообщения — ничего не менять; иначе зарегистрировать файл результата в s3_object (bucket_role = results,
created_by = <service.name>; только при наличии result_s3), обновить задание (status, service_name,
service_version, finished_at, metrics, error_code, error_message, result_schema_version, result_s3_object_id,
result_msg_id) и зафиксировать транзакцию. При status = error файл задания (если current_recognition_job_id равен
заданию) SHALL получить stored_file.status = ERROR; при done/needs_check файл остаётся PROCESSING и SHALL быть
обработан циклом маппинга (спека osv-result-mapping); inbox MUST NOT читать файл результата. Неизвестный job_id SHALL
быть залогирован. Результат для задания, уже находящегося в финальном статусе (done, needs_check, error,
timeout, dlq), SHALL логироваться и не применяться. Если в конверте есть raw_s3_key, ядро SHALL независимо от status зарегистрировать сырой ответ ИС в s3_object (bucket_role = raw, ключ raw/{job_id}.json) и проставить recognition_job.raw_s3_object_id. При status = error в той же транзакции SHALL записываться quality_failure_fact (failure_kind = SERVICE_ERROR, error_code из конверта).
Scenario: Успешный результат
- WHEN сервис публикует конверт
status = doneсresult_s3 = {arm-ka, results/<file_id>/<job_id>/result.json}иNats-Msg-Id = result:<job_id>:1 - THEN задание получает
status = done,result_msg_id = result:<job_id>:1,result_s3_object_idуказывает на новую строкуs3_objectс этим ключом,finished_atзаполнен; файл остаётсяPROCESSINGдо маппинга
Scenario: Повторная доставка
- WHEN то же сообщение получено второй раз
- THEN строка
recognition_jobиs3_objectне меняются
Scenario: Ошибка сервиса
- WHEN конверт содержит
status = error,error = {UNSUPPORTED_FORMAT, …}безresult_s3 - THEN задание получает
status = error,error_code = UNSUPPORTED_FORMAT,error_messageиз конверта,result_s3_object_idпуст; файл —ERROR
Scenario: Результат пришёл после таймаута
- WHEN задание уже
timeout, и приходит конвертdone - THEN задание не меняется, событие залогировано
Scenario: Сырой ответ зарегистрирован
- WHEN конверт
doneсодержитraw_s3_key = raw/<job_id>.json - THEN создан
s3_objectсbucket_role = raw,recognition_job.raw_s3_object_idуказывает на него
Scenario: Факт отказа сервиса
- WHEN конверт содержит
status = error - THEN кроме
ERRORфайла записанquality_failure_factсfailure_kind = SERVICE_ERROR
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 создавать новое задание по требованию «Новая попытка обработки файла». Каждый перевод в timeout и в dlq SHALL писать quality_failure_fact (TIMEOUT / NO_ACK) в той же транзакции.
Scenario: Сервис взял, но не ответил
- WHEN задание в
processingиreply_deadlineпрошёл без результата - THEN не позднее чем через 30 с задание имеет
status = timeout, файл —ERRORсerror_code = TIMEOUT
Scenario: Факт таймаута
- WHEN вахтёр перевёл задание в
timeout - THEN записан
quality_failure_factсfailure_kind = TIMEOUT