Решение владельца 08.10.2026: запросы — files.tabular_uploaded / files.img_uploaded
(было recognition.{tabular|image}.request), ack и результаты обоих сервисов —
files.tabular_parsed (было recognition.result); files.img_parsed не используется,
старый АРМ гасится. Тела сообщений, Nats-Msg-Id, queue group'ы и schema_version
не меняются.
Change nats-files-subjects: дельта recognition-contract (3 требования), Purpose
спеки; описания subject'ов в contracts/*.schema.json; 03-architecture (схема,
таблица потоков, NATS платформы), 09-delivery-plan, dev/README, dev/qa-guide,
repo-templates. DBML v5.17 не поднимается (R-3, deferred).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
7.8 KiB
MODIFIED Requirements
Requirement: Сообщение запроса распознавания
Тело сообщения на subject files.tabular_uploaded или files.img_uploaded SHALL соответствовать
docs/contracts/recognition-request.schema.json v1.0: обязательные поля schema_version ("1.0"), job_id (uuid =
recognition_job.id), file_id (uuid = stored_file.id), pipeline (tabular | image), file_type
(XLS | XLSX | XLSB | PDF | PNG | JPG), original_name, s3 {bucket, key} исходника в роли uploads,
reply_deadline (date-time, UTC), attempt (≥ 1). Subject SHALL соответствовать pipeline.
Scenario: Публикация запроса ядром
- WHEN ядро переводит
recognition_jobизqueuedвpublished - THEN в subject, соответствующий
recognition_job.pipeline, публикуется сообщение, проходящее схему запроса, с заголовкомNats-Msg-Id=<job_id>:<attempt>
Scenario: Сообщение не по схеме
- WHEN сервис получает запрос, не проходящий схему (нет
s3.key, неизвестныйfile_type), но из тела извлекаются корректныеjob_idиfile_id - THEN сервис публикует результат со
status = error,error.code = SCHEMA_MISMATCHбез предшествующего ackprocessingи не падает;attemptдляNats-Msg-Idрезультата берётся из тела запроса, при его отсутствии реализации расходятся (tabular-parserберётattemptиз заголовкаNats-Msg-Idзапроса или не отвечает,scan-gatewayподставляет 1) — см. OQ-71
Scenario: Нечитаемый запрос
- WHEN сервис получает сообщение, из которого нельзя извлечь
job_id(не JSON, не объект,job_idотсутствует или не uuid), а значит нельзя собрать валидный конверт - THEN сервис ответа не публикует: запрос записывается в лог и отбрасывается; ядро не получит ack, повторит
публикацию
OUTBOX_MAX_REPUBLISHраз и переведёт задание вdlq(error_code = NO_ACK)
Scenario: Невалидный повтор запроса, уже находящегося в работе
- WHEN сервис получает запрос, не проходящий схему, но с
job_id, который уже в работе или обработан этим процессом - THEN конверт
SCHEMA_MISMATCHне публикуется (егоNats-Msg-Idresult:<job_id>:<attempt>совпал бы с настоящим результатом, и ядро отбросило бы настоящий как дубликат); запрос записывается в лог и отбрасывается
Scenario: Примеры контракта — иллюстрации
- WHEN запрос берётся из
docs/contracts/examples/request-*.json(вrequest-image.jsonreply_deadlineлежит в прошлом) - THEN он проходит схему: схема проверяет для
reply_deadlineлишь форматdate-time, но не сравнивает срок с текущим временем; пример — иллюстрация формы сообщения, а не готовое к исполнению задание; проверка срока — дело сервиса (scan-gatewayотвечаетerror TIMEOUT, еслиreply_deadlineистёк до отправки файла в ИС Сканирование)
Requirement: Подтверждение взятия в работу (ack без JetStream)
Получив корректный запрос (прошедший проверку схемы и MAJOR), сервис SHALL немедленно, до чтения файла из S3, опубликовать в files.tabular_parsed конверт со
status = processing, job_id, file_id, service {name, version} и заголовком Nats-Msg-Id = ack:<job_id>:<attempt>.
Ядро SHALL перевести задание published → processing и записать started_at, service_name, service_version. Если
ack не получен за ACK_TIMEOUT_SECONDS (по умолчанию 30), ядро SHALL опубликовать запрос повторно с тем же
Nats-Msg-Id и тем же attempt, не более OUTBOX_MAX_REPUBLISH (по умолчанию 3) раз, после чего перевести задание в dlq.
Scenario: Сервис жив
- WHEN запрос опубликован и сервис подписан
- THEN в течение
ACK_TIMEOUT_SECONDSвfiles.tabular_parsedпоявляется конвертprocessing, задание получаетstatus = processingиstarted_at
Scenario: Сервис недоступен
- WHEN ни один инстанс сервиса не подписан на subject запроса
- THEN ядро публикует запрос повторно каждые
ACK_TIMEOUT_SECONDS; послеOUTBOX_MAX_REPUBLISHповторов задание получаетstatus = dlq,request_msg_idне меняется
Scenario: Повторный запрос пришёл в работающий сервис
- WHEN сервис получает запрос с
job_id, который уже обрабатывается или обработан этим процессом - THEN сервис публикует ack повторно и не начинает вторую обработку
Requirement: Сообщение результата распознавания
Тело сообщения на subject files.tabular_parsed SHALL соответствовать docs/contracts/recognition-result-message.schema.json
v1.0: обязательные поля schema_version ("1.0"), job_id, file_id, status (processing | done | needs_check |
error), service {name, version}; при status ∈ {done, needs_check} обязательны result_s3 {bucket, key} и
result_schema_version; при status = error обязателен error {code, message}; опциональны raw_s3_key
(только scan-gateway) и metrics. Поле group_schema в конверт NOT включается — источник recognition_job.group_schema
— файл результата.
Scenario: Успешный результат
- WHEN сервис завершил обработку файла
- THEN файл результата записан в S3 по ключу
results/{file_id}/{job_id}/result.json, а затем опубликован конверт соstatus = done | needs_check,result_s3указывает на этот файл, заголовокNats-Msg-Id=result:<job_id>:<attempt>
Scenario: Ошибка обработки
- WHEN сервис не смог обработать файл (нечитаем, таймаут внешней системы, тип не поддерживается)
- THEN публикуется конверт со
status = errorиerror.codeиз перечня схемы (UNSUPPORTED_FORMAT,UNREADABLE,TIMEOUT,SCHEMA_MISMATCH,NOT_IMPLEMENTED,FILE_READ_ERROR,INVALID_FORMAT,INVALID_DOCUMENT,RECOGNITION_FAILED,UNKNOWN_ERROR);result_s3может отсутствовать