## Context Решение владельца 12.10.2026: «топики можно выставлять через секреты и jwt токен тоже, т.к. у нас будут различные наборы топиков и разные jwt для прод и тест окружения». Выбран подход 1 из трёх обсуждённых (12.10.2026): 1. **Выбран:** три ключа в группе `OPENBAO__NATS__`, одинаковые в трёх сервисах. 2. `OPENBAO__APP__*` (`settings_sources.py`) — отвергнут: разносит JWT и subject'ы, на которые он даёт права, по разным группам. 3. Один префикс `NATS_SUBJECT_PREFIX` — отвергнут: работает, только если тестовые subject'ы платформы отличаются от боевых префиксом. Образец — `RSHB/armka_file_worker` (проверен в контуре): `NATSSettings` с `NATS_URL`, `NATS_JWT: SecretStr`, `NATS_SEED: SecretStr`, `NATS_COMMAND_SUBJECT`, `NATS_EVENT_SUBJECT`; `Settings(hide_input_in_errors=True)`. | Ключ | Умолчание | arm-core | tabular-parser | scan-gateway | |---|---|---|---|---| | `OPENBAO__NATS__NATS_TABULAR_REQUEST_SUBJECT` | `files.tabular_uploaded` | публикует | подписан (queue `tabular-parser`) | — | | `OPENBAO__NATS__NATS_IMAGE_REQUEST_SUBJECT` | `files.img_uploaded` | публикует | — | подписан (queue `scan-gateway`) | | `OPENBAO__NATS__NATS_RESULT_SUBJECT` | `files.tabular_parsed` | подписан (queue `arm-core`) | публикует | публикует | ## Goals / Non-Goals Goals: subject'ы и учётные данные NATS задаются секретами окружения; разъехавшиеся настройки видны в `/healthcheck`; тесты не делят subject'ы со стендом. Non-goals: см. proposal. ## Decisions 1. **Ключи одинаковые во всех трёх сервисах, проверяются все три.** На окружение — один набор секретов, который платформа кладёт всем сервисам; сервис использует только свои subject'ы, но валидирует все три, чтобы ошибка набора проявилась в любом сервисе одинаково. 2. **Валидация при старте.** Имя subject'а — непустое, без пробельных символов, без шаблонов `*` и `>`, без пустых токенов (`a..b`, ведущая или завершающая точка). Три имени попарно различны: совпадение запроса и ответа — сервис получает собственные ответы; совпадение двух запросов — каждый запрос получают оба сервиса. Ошибка — сервис не стартует (ошибка конфигурации, как у `DB_SCHEMA`). 3. **Константы `contract/subjects.py` — только умолчания.** Код публикации и подписок получает имена из настроек; функция выбора subject'а по `pipeline` принимает настройки (форма — на усмотрение плана сервиса). 4. **Секреты.** `NATS_JWT` и `NATS_SEED` — `SecretStr`; в колбэки nkeys значение передаётся через `get_secret_value()`; `Settings` с `hide_input_in_errors=True`, чтобы ошибка валидации конфигурации не печатала входные значения. 5. **`/healthcheck`.** Новый объект `subjects` с subject'ами, которые использует сервис: arm-core — `tabular_request`, `image_request`, `result`; tabular-parser — `tabular_request`, `result`; scan-gateway — `image_request`, `result`. Отдаётся всегда, в том числе при `nats = off | down` (это конфигурация, не состояние). Решение владельца 12.10.2026: «пусть будет в хэлзчеке». 6. **Лог при старте.** Сервис пишет свои subject'ы одной строкой при старте (существующая строка «Подписка NATS активна» сохраняется). 7. **Тесты (OQ-83).** Интеграционные тесты `tabular-parser` и `scan-gateway`, где обе стороны обмена — в самом тесте, задают уникальные subject'ы через настройки и не пересекаются с контейнерами стенда. e2e ядра (`make e2e`) остаётся на subject'ах по умолчанию: он гоняет настоящий контейнер `tabular-parser` стенда, который слушает умолчания, а конфликт там — в общей queue group `arm-core` на subject'е ответа; требование останавливать ядро стенда сохраняется (R-7). Интеграционные тесты NATS ядра уже работают на уникальных subject'ах. ## Risks / Trade-offs - Разъехавшиеся настройки ядра и сервиса — тишина на шине: ядро переведёт задания в `dlq` по `NO_ACK`. Смягчение: `subjects` в `/healthcheck` и лог при старте; в `03-architecture.md` — требование задавать набор всем трём сервисам. - JWT окружения без прав на заданные subject'ы — ошибка прав NATS при подписке/публикации (в логе сервиса); смягчение — перечень секретов окружения в `03-architecture.md` и `contour-transfer-guide.md`. - `/healthcheck` публичен и теперь раскрывает имена subject'ов. Имена не секретны (без JWT ими не воспользоваться), JWT и seed в ответ не попадают. ## Decisions made without discussion 1. 12.10.2026 — **R-1:** имена ключей `NATS_TABULAR_REQUEST_SUBJECT`, `NATS_IMAGE_REQUEST_SUBJECT`, `NATS_RESULT_SUBJECT` (по образцу FileWorker — префикс `NATS_`, суффикс `_SUBJECT`), а не `NATS_COMMAND_SUBJECT` / `NATS_EVENT_SUBJECT` FileWorker: у нас два subject'а запроса. Имена показаны владельцу в дизайне и приняты вместе с подходом. Цена ошибки: низкая — переименование ключа до выката в контур. 2. 12.10.2026 — **R-2:** `schema_version` не меняется: тела сообщений те же, subject'ы — окружение (как R-2 в `nats-files-subjects`). Цена ошибки: низкая. 3. 12.10.2026 — **R-3:** проверяются все три subject'а в каждом сервисе, включая неиспользуемые им (решение 1). Цена ошибки: низкая — лишний отказ старта при кривом наборе, который всё равно сломал бы обмен. 4. 12.10.2026 — **R-4:** `subjects` в `/healthcheck` отдаётся и при `nats = off`. Цена ошибки: низкая. 5. 12.10.2026 — **R-5:** пароли MinIO/SMB в `SecretStr` не переводятся — вне запроса владельца; `hide_input_in_errors` всё равно скрывает их в ошибках валидации. Цена ошибки: пароли видны в `repr(settings)` до отдельной задачи. 6. 12.10.2026 — **R-6:** маршрут — OpenSpec change `nats-env-config` в каждом из четырёх репозиториев, один план SDD на все четыре (больше пяти задач — SDD-контроллер). Цена ошибки: низкая, процессная. 7. 12.10.2026 — **R-7:** e2e ядра не переводится на свои subject'ы (решение 7): ему нужен контейнер `tabular-parser` стенда на умолчаниях, а развести ядро теста и ядро стенда может только своя queue group, которая в этом change не настраивается. OQ-83 для `make e2e` остаётся открытым. Цена ошибки: перед `make e2e` по-прежнему останавливать ядро стенда.