По ответам пользователя 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>
9.4 KiB
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сервиса падает с указанием имени файла