diff --git a/context/03-architecture.md b/context/03-architecture.md index 9bc4e22..a11ac65 100644 --- a/context/03-architecture.md +++ b/context/03-architecture.md @@ -42,7 +42,11 @@ и [`recognition-result-message.schema.json`](../contracts/recognition-result-message.schema.json) — тела сообщений, [`recognition-result.schema.json`](../contracts/recognition-result.schema.json) — файл результата в S3; примеры — [`docs/contracts/examples/`](../contracts/examples/). Примеры проверяются схемами: `python scripts/validate_contracts.py` -(из корня репозитория docs). +(из корня репозитория docs). Примеры — иллюстрации формы сообщений, а не готовые к исполнению задания: +схема проверяет у `reply_deadline` только формат `date-time` и не сравнивает срок с текущим временем, поэтому +`request-image.json` проходит схему при `reply_deadline` в прошлом. Срок проверяет сервис: `scan-gateway` отвечает +`error TIMEOUT`, если `reply_deadline` истёк до отправки файла в ИС Сканирование; `tabular-parser` срок не проверяет. +Файлы `examples/` копируются в сервисы побайтно, поэтому пример не правится ради «свежей» даты. | Поток | Subject | Кто пишет | Полезная нагрузка | `Nats-Msg-Id` | Схема | |---|---|---|---|---|---| @@ -57,7 +61,9 @@ ядро — queue group `arm-core` на `recognition.result`; сервис сразу по получении запроса публикует ack `processing`; ядро, не получив ack за `ACK_TIMEOUT_SECONDS` (30), публикует запрос повторно с тем же `Nats-Msg-Id`, не более `OUTBOX_MAX_REPUBLISH` (3) раз, затем `dlq`. Сервис держит process-local множество `job_id` в работе и на повторный -запрос отвечает ack'ом без второй обработки. Появится JetStream — меняются только publisher/subscriber. +запрос отвечает ack'ом без второй обработки. Публикация ack и результата сервисом при временной ошибке повторяется +с тем же `Nats-Msg-Id`: первая попытка плюс `PUBLISH_RETRIES` повторов (по умолчанию 3, всего до 4 попыток); повторы +исчерпаны — error-лог, задание закроет ядро (по `reply_deadline`). Появится JetStream — меняются только publisher/subscriber. Правила: - `recognition_job` создаётся в статусе `queued` **в одной транзакции** со `stored_file`; фоновый публикатор переводит в @@ -70,8 +76,12 @@ Ack (`processing`) сервис публикует **до чтения файла из S3**; повторная публикация запроса ядром идёт с тем же `Nats-Msg-Id` и тем же `attempt`. Дубликат результата ядро определяет по `recognition_job.result_msg_id`. - `schema_version` — `MAJOR.MINOR`; ядро и сервисы принимают тот же MAJOR и любой MINOR. Сервис отвергает запрос с - неизвестным MAJOR результатом `error` с кодом `SCHEMA_MISMATCH` и подтверждает его ack'ом, чтобы не зациклить - доставку. Новый MINOR раскатывается сначала на потребителей + неизвестным MAJOR (как и запрос не по схеме) результатом `error` с кодом `SCHEMA_MISMATCH` **без предшествующего ack + `processing`**: финальный конверт сам закрывает задание в ядре (`published → error`), повторной доставки не будет. + Конверт публикуется, только если из тела извлекаются корректные `job_id` и `file_id`; для `job_id`, уже находящегося в + работе или обработанного, он не публикуется (его `Nats-Msg-Id` совпал бы с настоящим результатом). Нечитаемый запрос + (не JSON, нет `job_id`) остаётся без ответа: сервис пишет лог и отбрасывает его, ядро не получает ack, повторяет + публикацию `OUTBOX_MAX_REPUBLISH` раз и закрывает задание в `dlq` с `error_code = NO_ACK`. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения. - Сервисы не знают про БД и про бизнес-смысл: они возвращают структуру файла; нормализация счетов, резолв