Andrey Epifancev da9b9f66cf openspec: change nats-env-config — subject'ы и JWT NATS по окружению из OpenBao
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:38:50 +04:00

9.5 KiB
Raw Blame History

ADDED Requirements

Requirement: Subject'ы NATS — настройки окружения

Subject'ы обмена SHALL задаваться секретами группы OPENBAO__NATS__ с одинаковыми ключами во всех трёх сервисах: NATS_TABULAR_REQUEST_SUBJECT (по умолчанию files.tabular_uploaded), NATS_IMAGE_REQUEST_SUBJECT (по умолчанию files.img_uploaded), NATS_RESULT_SUBJECT (по умолчанию files.tabular_parsed). У ядра и сервисов одного окружения значения MUST совпадать. Каждый сервис SHALL при старте проверять все три имени: непустое, без пробельных символов, без шаблонов * и >, без пустых токенов, три имени попарно различны; при нарушении сервис MUST NOT стартовать. Учётные данные NATS (OPENBAO__NATS__NATS_JWT, OPENBAO__NATS__NATS_SEED) SHALL задаваться по окружению теми же секретами, и их значения MUST NOT попадать в логи, repr настроек и тексты ошибок валидации конфигурации. Каждый сервис SHALL писать используемые subject'ы в лог при старте и отдавать их в /healthcheck объектом subjects (ключи tabular_request, image_request, result — только те, что использует сервис) независимо от состояния NATS. Queue group'ы и schema_version от настроек subject'ов не зависят.

Scenario: Умолчания

  • WHEN сервис стартует без ключей OPENBAO__NATS__NATS_*_SUBJECT
  • THEN обмен идёт по files.tabular_uploaded, files.img_uploaded, files.tabular_parsed

Scenario: Набор тестового окружения

  • WHEN всем трём сервисам заданы одинаковые OPENBAO__NATS__NATS_TABULAR_REQUEST_SUBJECT, NATS_IMAGE_REQUEST_SUBJECT, NATS_RESULT_SUBJECT, NATS_JWT, NATS_SEED тестового окружения
  • THEN ядро публикует запросы и получает ack и результаты только по этим subject'ам; subject'ы по умолчанию не используются, recognition_job.request_subject содержит subject тестового окружения

Scenario: Недопустимый набор

  • WHEN NATS_RESULT_SUBJECT совпадает с одним из subject'ов запроса, или имя содержит *, > или пробел
  • THEN сервис не стартует с ошибкой конфигурации, называющей ключ

Scenario: Разъехавшиеся настройки видны

  • WHEN у ядра и сервиса заданы разные subject'ы ответа
  • THEN /healthcheck.subjects.result ядра и сервиса различаются; JWT и seed в ответе /healthcheck отсутствуют

Scenario: Ошибка конфигурации не раскрывает JWT

  • WHEN сервис не стартует из-за ошибки валидации настроек при заданных NATS_JWT и NATS_SEED
  • THEN текст ошибки и лог не содержат значений JWT и seed

MODIFIED Requirements

Requirement: Сообщение запроса распознавания

Тело сообщения на subject запроса tabular или image (настройки окружения NATS_TABULAR_REQUEST_SUBJECT / NATS_IMAGE_REQUEST_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: Сообщение результата распознавания

Тело сообщения на subject ответа (настройка окружения NATS_RESULT_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 может отсутствовать