From f46a34169cfbac91b2ccc8f95043c60480f7fde9 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 17:21:59 +0400 Subject: [PATCH] =?UTF-8?q?docs(context):=20NATS=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BB=D0=B0=D1=82=D1=84=D0=BE=D1=80=D0=BC=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=20armka=5Ffile=5Fworker,=20=D0=BE=D0=B4=D0=B8=D0=BD=20JWT,=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0=20=D0=B3?= =?UTF-8?q?=D0=BE=D1=80=D1=8F=D1=87=D0=B8=D1=85=20=D0=BF=D0=B0=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 03-architecture: core NATS без JetStream подтверждён кодом armka_file_worker (работает в контуре); один JWT на все сервисы с правами по subject'ам; старое семейство subject'ов files.*; соглашение ROOT_PATH платформы Aurora; FileWorker не реализует DECISION-4 — только инфраструктурный референс. - 03-architecture: описание scan-gateway — эталон раскладки горячих папок ИС Сканирование как у FileWorker (Input/., *.tmp рядом, Output//*.json, очистка); расхождение кода — arm-scan-gateway #6. - 07-open-questions: OQ-85 дополнен, новый OQ-137 (раскладка папок) закрыт владельцем 06.10.2026. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- context/03-architecture.md | 35 +++++++++++++++++++++++++++++++---- context/07-open-questions.md | 3 ++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/context/03-architecture.md b/context/03-architecture.md index f779d63..1de3586 100644 --- a/context/03-architecture.md +++ b/context/03-architecture.md @@ -30,9 +30,13 @@ пишет нормализованный JSON в S3 `results/`, публикует `recognition.result`. Контракт результата — `docs/contracts/recognition-result.schema.json` (контракт v1.0). Правила распознавания заголовков/группировок — `rules/*.yaml`. -- **scan-gateway** — адаптер ИС Сканирование: переименовать файл `UUID_имя.pdf`, положить в «Входящие», опросить - «Исходящие/UUID/UUID.json», сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), нормализовать - в тот же контракт результата, опубликовать `recognition.result`. Таймаут 5 мин → `timeout`. +- **scan-gateway** — адаптер ИС Сканирование: положить файл в горячую папку `Input/.` (временный + `<имя>..tmp` рядом, затем атомарный rename; ИС игнорирует `*.tmp`), дождаться батча `Output//*.json` + (появляется целиком и больше не меняется), сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), + очистить `Output//` и входящий файл, нормализовать в тот же контракт результата, опубликовать + `recognition.result`. Таймаут 5 мин → `timeout`. Раскладка папок — как у проверенного в контуре PDFWorker старого АРМ + (`RSHB/armka_file_worker`, решение владельца 06.10.2026, OQ-137); текущий код шлюза именует файл `_` + и пишет временный файл в `Tmp/` — приводится к эталону в arm-scan-gateway #6. - **Frontend** — React + TS по шаблону команды; экраны по макету `dkb_24.09.26` (справочник ГК, консолидации, первичные документы, проверка распознавания, сводная ОСВ, шахматка, корректировки, гудвилл, КФО, ЕХ, журнал, роли). @@ -56,7 +60,8 @@ `group_schema` в конверт не входит — ядро берёт его из `documents[].group_schema` файла результата при маппинге. -**На платформе NATS без JetStream** (ответ 03.10.2026): нет durable consumer'ов, серверного дедупа и redelivery. +**На платформе NATS без JetStream** (ответ 03.10.2026; подтверждено 06.10.2026 кодом `RSHB/armka_file_worker`, который +работает в контуре на `subscribe`/`publish` + `flush` без `jetstream()`): нет durable consumer'ов, серверного дедупа и redelivery. Поэтому гарантии доставки — на уровне приложения: сервисы подписаны queue group'ами (`tabular-parser`, `scan-gateway`), ядро — queue group `arm-core` на `recognition.result`; сервис сразу по получении корректного запроса публикует ack `processing`; ядро, не получив ack за `ACK_TIMEOUT_SECONDS` (30), публикует запрос повторно с тем же `Nats-Msg-Id`, не более @@ -277,6 +282,9 @@ FastAPI» платформы РАИСА/aurora): дерево импортиро - **NATS** — `nats-py`; аутентификация nkeys-seed + JWT (`NATS_SEED`, `NATS_JWT`) через `user_jwt_cb`/`signature_cb`; соединение и подписки создаются в lifespan и живут в `app.state.nats` / `app.state.nats_subs`. Core NATS без JetStream: подписки с queue group, заголовки `Nats-Msg-Id` ставим сами, ack и повторы — на уровне приложения. + Учётные данные одни на все сервисы АРМ: один JWT с правами по subject'ам (ответ владельца 06.10.2026), поэтому новые + subject'ы (`recognition.*`) должны быть внесены в права этого JWT платформой; старый АРМ и `armka_file_worker` + работают на семействе `files.*` (`files.tabular_uploaded`, `files.tabular_parsed`, `files.img_parsed`). - **SMB** (горячие папки ИС Сканирование) — `smbprotocol`/`smbclient`, сессия регистрируется в lifespan; нужен только `scan-gateway`. - **Внешняя аутентификация** — пакет `raisa_fastapi_protected_api` из внутреннего Nexus (`AuthorizationMiddleware`, `ProtectedSettings`, …), импортируется только при `DEBUG=false`; фронтовая пара — `raisa-fastapi-protected-web`. @@ -357,3 +365,22 @@ JetStream** (3 subject `recognition.*`; аутентификация nkeys/JWT, префиксы-роли + lifecycle по префиксам), Python 3.11 в базовом образе, Nexus (pip/npm, пакеты `raisa_*`) — только внутри банка, CI/CD — пайплайн платформы (мы отдаём команды `make check` / `make check-full`). Секреты — через переменные окружения (`OPENBAO__*`). Открыто: версия Node, параметры JWT РАИСА. + +Дополнено 06.10.2026 по репозиторию `RSHB/armka_file_worker` (PDFWorker старого АРМ, проверен в контуре банка) и ответам +владельца: +- NATS: core NATS без JetStream подтверждён кодом, работающим в контуре (`nats-py[nkeys] 2.14`, `nats.connect` с + `user_jwt_cb`/`signature_cb`, `max_reconnect_attempts=-1`; учётные данные опциональны — оба пустых означают анонимное + подключение). Один JWT на все сервисы АРМ, права по subject'ам заданы в JWT. Можно ли включить JetStream — по-прежнему + OQ-85. +- Переменные окружения те же, что в шаблоне: `OPENBAO__NATS__{NATS_URL, NATS_JWT, NATS_SEED}`, `OPENBAO__MINIO__*`, + `OPENBAO__SMB__*`, вложенность через `__`. +- Приложения на платформе Aurora живут под префиксом `ROOT_PATH = /aurora/apps/fastapi-dkb-armka-` (у + FileWorker — `fastapi-dkb-armka-pdf-worker`); наши сервисы держат `ROOT_PATH = /` по умолчанию и получают префикс из + окружения. Healthcheck `GET /healthcheck` отвечает `503`, если NATS отключён или воркер остановлен — тот же принцип, + что у нас. +- Горячие папки ИС Сканирование: эталон — раскладка FileWorker (`Input/.`, `*.tmp` рядом, + `Output//*.json`, очистка после чтения), см. описание scan-gateway выше и OQ-137. +- FileWorker **не** реализует наш контракт DECISION-4: subject'ы `files.*`, конверт `task_id`/`file_id`/`source_s3_key`/ + `expires_at` → `status ready|timeout|error` + `error_code`, без ack `processing`, без `Nats-Msg-Id`, без queue group + (один экземпляр), дедуп только в памяти процесса — доставка at-most-once. Это параллельный сервис старого АРМ, а не + замена scan-gateway; из него берём только инфраструктурные решения (как и из `reference-old-arm`). diff --git a/context/07-open-questions.md b/context/07-open-questions.md index 714db22..fc38978 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -90,7 +90,7 @@ | OQ-82 | `.dockerignore` в `arm-core` не исключает `api/.env.test`: файл попадает в образ (у `tabular-parser` и `scan-gateway` исключён). Исправлять? | arm-core `.dockerignore`; docs: T3.2 (задача 3 фазы 2) | срез 0 (образы), прод-сборка | **закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026):** `.dockerignore` исключает `api/.env.*` (включая `.env.test` и `.env.example`); настройки контейнера — только из окружения | | OQ-83 | Контейнеры профиля `services` подписаны на те же subject и queue group общего NATS, что и экземпляры в интеграционных тестах сервисов (`recognition.result` / `arm-core`, `*.request`): NATS может отдать сообщение теста контейнеру и наоборот; интеграционные тесты `scan-gateway` и `arm-core` одновременно тоже делят NATS. Сделать queue group и subject-префикс настраиваемыми для тестов (например `NATS_TEST_PREFIX`) или поднимать отдельный NATS на тесты? | scan-gateway: п. 11 таблицы предложений (T14.1, T11.8); docs: O-11 (задача 3 фазы 2; подтверждено ревью для tabular-parser и scan-gateway), `dev/README.md` | срез 0 (процесс), интеграционные тесты | допущение принято: в `dev/README.md` предупреждение и команда `stop` для контейнеров профиля; не гонять `make test-integration` двух сервисов одновременно | | OQ-84 | Запуск сервисов из venv без Nexus (`make venv` + `python run_server.py` из `api/` с `.env` из `docs/dev/.env.example`, healthcheck по `localhost`) в Срезе 0 не проверялся ни в одном из трёх сервисов. Известно по чтению конфигов (design #44): `.env.example` в нынешнем виде отвергнут `scan-gateway` (`OPENBAO__DATABASE__*`) и `tabular-parser` (`OPENBAO__DATABASE__*`, `OPENBAO__SMB__*` — `extra_forbidden`), в нём нет `OPENBAO__MINIO__MINIO_SECURE=false` (по умолчанию `true` — https к http-MinIO). Прогнать сценарий, зафиксировать набор переменных и портов (`SERVER__PORT`, `SCAN_HOTFOLDER_PATH` для `scan-gateway`), решить, нужен ли отдельный `.env.example` на сервис, и снять пометку «черновик» с раздела `dev/README.md`? | spec `dev-environment`, сценарий «Запуск из venv против compose без Nexus»; docs: design #43, `dev/README.md` («Запуск сервисов из venv без Nexus»); финальное ревью фазы 2: I-1 | срез 0 (процесс, окружение разработки); кандидат в follow-up issue | вопрос: допущение Среза 0 — раздел README остаётся черновиком, сценарий не подтверждён (проверка требовала `.venv` внутри сервисных репозиториев, в фазе 2 доступных только для чтения) | -| OQ-85 | **Возможен ли JetStream на платформе** (серверный дедуп по `Nats-Msg-Id`, durable consumer'ы, redelivery вместо ack и повторной публикации на уровне приложения)? Сейчас NATS без JetStream (ответ платформы 03.10.2026); Срез 0 строит доставку на ack `processing`, повторной публикации ядром и дедупе по `recognition_job.result_msg_id`. Если JetStream подключить можно — меняются только publisher/subscriber (`core/nats.py` в трёх сервисах), outbox/inbox и контракт остаются; исчезают потеря результата при простое ядра и риск повторной обработки. | решение человека 04.10.2026: «узнать у платформы и проверить позже»; `design.md` slice-0-skeleton (D-3, Open Questions п. 4); OQ-72, OQ-73 | инфраструктура NATS (платформа); `core/nats.py`, воркеры сервисов | **вопрос к платформе (поставить напоминание, проверить после ревью Среза 0 / до Среза 3)**: допущение — без JetStream | +| OQ-85 | **Возможен ли JetStream на платформе** (серверный дедуп по `Nats-Msg-Id`, durable consumer'ы, redelivery вместо ack и повторной публикации на уровне приложения)? Сейчас NATS без JetStream (ответ платформы 03.10.2026); Срез 0 строит доставку на ack `processing`, повторной публикации ядром и дедупе по `recognition_job.result_msg_id`. Если JetStream подключить можно — меняются только publisher/subscriber (`core/nats.py` в трёх сервисах), outbox/inbox и контракт остаются; исчезают потеря результата при простое ядра и риск повторной обработки. | решение человека 04.10.2026: «узнать у платформы и проверить позже»; `design.md` slice-0-skeleton (D-3, Open Questions п. 4); OQ-72, OQ-73 | инфраструктура NATS (платформа); `core/nats.py`, воркеры сервисов | **вопрос к платформе (поставить напоминание, проверить после ревью Среза 0 / до Среза 3)**: допущение — без JetStream. 06.10.2026: `RSHB/armka_file_worker` (PDFWorker старого АРМ, проверен в контуре) тоже работает на core NATS (`subscribe`/`publish` + `flush`, без `jetstream()`), один JWT на все сервисы АРМ с правами по subject'ам; возможность включить JetStream по-прежнему не выяснена | | OQ-86 | Фильтр видимости по ЗО (`business_segment`): сегмент есть только у `app_user`, у ГК/ЮЛ поля нет (DBML v5.14). Откуда брать сегмент ГК (из ЕЦП CRM? из РФ?), нужен ли вообще фильтр по ЗО в первой очереди? | §10.1 правил; DBML v5.14 | срез 1 (policy), DBML v5.15 | допущение: в Срезе 1 ЗО не фильтрует; видимость — только по филиалу (ГО видит всё) | | OQ-87 | Выгрузка ЕЦП содержит типы `нерез.` (354), `ФЛ` (180), `нет инн` (2); enum `entity_type` — только ЮЛ/ИП. Нужны ли нерезиденты и физлица в справочнике (контрагенты ОСВ, поручители)? | `docs/source/biq/ecp-export-lagal-entity.xlsx`; §13.2 | срез 1 (импорт), DBML | допущение: строки пропускаются с кодом `UNSUPPORTED_TYPE`, enum не расширяется | | OQ-88 | Проверять ли контрольные разряды ИНН при импорте и вводе (сейчас только длина 10/12); в выгрузке 279 нечисловых значений (`LV…`, `РНН…`, `нерез.`) | §13.2 | срез 1 | допущение: только длина; нечисловые → `INVALID_INN` | @@ -142,6 +142,7 @@ | OQ-134 | Шапка ОСВ без строки организации и с кодом/названием счёта в двух колонках («Счет: Код / Наименование», строка «Бухгалтерский \| 9 месяцев 2025 г.»; Новоросметалл ОСВ 63): парсер не находит организацию, счёт, период, тип данных. Новый вариант заголовка — словарь или код | пакет ДКБ 06.10.2026 | tabular-parser `detect.py`/`header.py`, `osv_headers.yaml` | открыт: arm-tabular-parser#11 п. 4 | | OQ-135 | Отчёт 1С «Анализ счета» с отбором по контрагентам группы (`fixtures/analysis-reports/`, в именах файлов «ОСВ_…ПоКомпаниямГруппы»): не ОСВ, но содержит ВГО по корреспонденции счетов. Нужен ли отдельный тип документа для Среза 5, или ВГО считаются только из ОСВ | пакет ДКБ 06.10.2026 | `document_type`, Срез 5 | открыт: допущение — не разбирать; парсер отвечает `INVALID_DOCUMENT` | | OQ-136 | ИНН организаций пакета ДКБ (Цимлянский, НКЗ, Новоросметалл, Приманыческий, Агрокомплекс) в шапках ОСВ отсутствуют; для теста консолидации по этим группам нужны ИНН и состав ГК/ГСК (выгрузка ЕЦП или список от ДКБ) | пакет ДКБ 06.10.2026 | seed/фикстуры справочников, тесты Среза 4–5 на новых группах | открыт | +| OQ-137 | Раскладка горячих папок ИС Сканирование: scan-gateway кладёт `Input/_` через временный `Tmp/.<имя>.part`, ничего не удаляет; PDFWorker старого АРМ (`RSHB/armka_file_worker`, проверен в контуре) кладёт `Input/.` через временный `<назначение>..tmp` рядом (ИС игнорирует `*.tmp`), читает батч `Output//*.json` и удаляет `Output//` и входящий файл после чтения. Какая раскладка актуальна? | сравнение `armka_file_worker` (README «Файлы», `clients.py`) и `scan-gateway/gateway/hotfolder.py`, `smb.py`; OQ-78, OQ-79 | scan-gateway (спека `image-recognition-gateway`, эмулятор, тесты) | **закрыт владельцем 06.10.2026: «правильно как в FileWorker»** — имя `.`, временный `*.tmp` рядом с назначением (каталог `Tmp` из OQ-79 не нужен), очистка `Output//` и входящего файла после чтения; `Output//*.json` батчем и стабилизация набора (OQ-78) остаются. Реализация — arm-scan-gateway #6 | Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения; `DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4. -- 2.47.2