Andrey Epifancev a0be5ab5a1 docs: Срез 0 под факты платформы — NATS без JetStream, Python 3.11, PostgreSQL 16, без CI и Nexus
По ответам пользователя 03.10.2026: обмен через recognition_job перестроен на core NATS (queue group'ы,
ack сервиса = конверт status=processing, повторная публикация ядром 30 с × 3 → dlq, дедуп по result_msg_id);
capability ci-template заменена на quality-checks (make check / make check-full, конфигураций CI нет);
compose: PostgreSQL 16, NATS без --jetstream, python:3.11-slim; пакеты raisa_* фильтруются при dev-установке,
npm raisa-* → optionalDependencies. Обновлены 03-architecture, 09-delivery-plan, 00-workspace-layout,
proposal/design/specs/tasks change'а; открытые вопросы к платформе закрыты, остались Node и JWT РАИСА.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 16:21:39 +04:00

9.4 KiB
Raw Blame History

Spec Delta: recognition-contract

Purpose

Контракт обмена между ядром arm-core и stateless-сервисами распознавания (tabular-parser, scan-gateway): тела сообщений NATS recognition.{tabular|image}.request и recognition.result, файл результата в S3 и правила версионирования и идемпотентности. Сервисы не знают БД; ядро — единственный писатель (DECISION-4).

ADDED Requirements

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

Тело сообщения на subject recognition.tabular.request или recognition.image.request 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)
  • THEN сервис публикует результат со status = error, error.code = SCHEMA_MISMATCH и не падает

Requirement: Подтверждение взятия в работу (ack без JetStream)

Получив запрос, сервис SHALL немедленно, до чтения файла из S3, опубликовать в recognition.result конверт со 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 в recognition.result появляется конверт 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 recognition.result 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 может отсутствовать

Requirement: Файл результата

Файл результата SHALL соответствовать docs/contracts/recognition-result.schema.json v1.0: один файл (stored_file) → один job_id → массив documents из osvDocument и/или statementDocument; денежные значения — строки ^-?\d+(\.\d{1,2})?$; confidence — 0–100. Пустой массив documents допустим только при status = error.

Scenario: Результат валидируется примерами

  • WHEN CI сервиса запускает тесты контракта
  • THEN все файлы docs/contracts/examples/result-*.json проходят схему файла результата, а все request-*.json / result-message-*.json — схемы сообщений

Requirement: Версионирование контракта

Поле schema_version SHALL быть строкой MAJOR.MINOR. Добавление необязательных полей или значений enum — MINOR (1.1, обратно совместимо); удаление/переименование полей, смена типов или семантики — MAJOR (2.0). Сервис SHALL отвергать запрос с неизвестным MAJOR (error.code = SCHEMA_MISMATCH); ядро SHALL принимать результат с тем же MAJOR и любым известным или новым MINOR.

Scenario: Несовместимая версия запроса

  • WHEN сервис, поддерживающий 1.x, получает запрос schema_version = "2.0"
  • THEN публикуется результат status = error, error.code = SCHEMA_MISMATCH, запрос подтверждается (ack), чтобы не зациклить доставку

Requirement: Идемпотентность результатов без брокера

Публикующий SHALL ставить заголовок Nats-Msg-Id на каждое сообщение (запрос <job_id>:<attempt>, ack ack:<job_id>:<attempt>, результат result:<job_id>:<attempt>). Поскольку серверного дедупа нет, ядро SHALL считать результат дубликатом, если recognition_job.result_msg_id уже равен Nats-Msg-Id сообщения, и MUST NOT менять задание повторно; потерянный результат SHALL обнаруживаться по reply_deadline (timeout).

Scenario: Результат доставлен дважды

  • WHEN сервис опубликовал один и тот же результат два раза (повтор публикации после сетевой ошибки)
  • THEN задание изменено один раз, второе сообщение проигнорировано

Requirement: Копия контракта в сервисе идентична источнику

Каждый сервис, читающий или пишущий контракт, SHALL хранить копию файлов схем в api/contracts/ и проверять в make check их байтовую идентичность файлам docs/contracts/ (../docs или путь ARM_KA_DOCS; при недоступности — пропуск с предупреждением).

Scenario: Копия устарела

  • WHEN в docs/contracts/ изменена схема, а копия в сервисе — нет
  • THEN тест идентичности в make check сервиса падает с указанием имени файла