Andrey Epifancev 50e588b6bd contracts: subject'ы NATS как в старом АРМ — files.*_uploaded, files.tabular_parsed (#74)
Решение владельца 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
2026-10-08 23:34:01 +04:00

7.8 KiB
Raw Blame History

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 без предшествующего ack processing и не падает; 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-Id result:<job_id>:<attempt> совпал бы с настоящим результатом, и ядро отбросило бы настоящий как дубликат); запрос записывается в лог и отбрасывается

Scenario: Примеры контракта — иллюстрации

  • WHEN запрос берётся из docs/contracts/examples/request-*.json (в request-image.json reply_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 может отсутствовать