## 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'е ответа; публикации запросов в тестах ядра (`test_recognition_exchange.py`, `test_api_files_upload.py`) идут на умолчательные 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` по-прежнему останавливать ядро стенда. 8. 13.10.2026 — **R-8:** задачи 2/4/6 (настройки сервисов) и затем 3/5/7 выполнялись параллельно по репозиториям (общих файлов нет); ревью — по каждой задаче отдельно. Цена ошибки: нулевая (разные репозитории). 9. 13.10.2026 — **R-9:** трейлер коммитов — `Co-Authored-By: Claude Opus 5.5` по вводной; у двух коммитов исполнителей (haiku) был Haiku 5.5 — исправлено `commit --amend` до публикации ветки. Цена ошибки: нулевая. 10. 13.10.2026 — **R-10:** minor-находки задач, усиливающие тесты (T2.1, T2.2, T3.1, T3.2, T4.1, T5.1–T5.3, T5.6, T6.1, T6.3, T8.2, T8.3), исправлены одной волной после задач 1–8; косметика (T7.1 — позже исправлена, T7.2, T5.4 — позже исправлена) и T8.1 отложены/отклонены в ledger. Тест T2.2 (утечка JWT при невалидном subject) не ловит отсутствие `hide_input_in_errors` (ошибка field-validator не печатает вход группы) — оставлен как регрессионный; защиту несёт `test_validation_error_hides_nats_secrets`. Цена ошибки: низкая. 11. 13.10.2026 — **R-11:** по финальному ревью (opus) исправлены I-1 (повторная публикация обновляет `recognition_job.request_subject`), M-1 (примеры `/healthcheck`), M-2 (формулировка про интеграционные тесты ядра — запросы тестов ядра идут на умолчательные subject'ы), M-3 (docstring'и и логи не называют умолчание фактическим subject'ом), M-5 (пометки «по умолчанию» в CLAUDE.md и шаблонах). **M-4** (базовые спеки с `SHALL … files.*` формально противоречат ADDED-требованию) — отложено: решение при архивации (MODIFIED) или отдельным change'ем; **M-7** (ключи в `.env.example` сервисов закомментированы, у ядра — нет) отклонено: у ядра паритет `.env.example`/`.env.test` проверяет тест, в сервисах умолчание работает без ключей. Цена ошибки M-4: после архивации в базовых спеках остаются два формально разных требования о subject'ах; M-7: нулевая. 12. 13.10.2026 — **R-12:** `make check` в docs запускался с `CHANGE=nats-env-config` — дефолт `CHANGE ?= slice-0-skeleton` указывает на архивный change (O-2, вне объёма). Цена ошибки: нулевая. 13. 13.10.2026 — **R-13:** `check-full` в tabular-parser проверен на временной ветке `main` + feat/31: ветка отстаёт от main (tp#33), общий `fixtures/` уже содержит golden после fixtures#18; на ветке без merge main 2 теста `tests/fixtures` красные, после merge — 2313 passed. Ветку не перебазировал. Цена ошибки: перед merge PR — тот же расклад, решение (rebase/merge main) за владельцем. 14. 13.10.2026 — **R-14:** после отчёта контроллера ветки docs `feat/80` и tabular-parser `feat/31` (ещё не опубликованные) перебазированы на свежий `main` (docs#98, tp#33) — снимает расклад R-13 без merge-коммита; force-push не нужен. `check-full` сервисов и `make check` docs перезапущены на перебазированных ветках. Цена ошибки: нулевая. 15. 13.10.2026 — **R-15:** отложенные контроллером RM-1 (лог `file_mismatch` с литералом `files.tabular_parsed`) и RM-2 (переносы docstring'ов в arm-core) исправлены сразу — правка в три строки, follow-up не нужен. T8.1 сверено с кодом ядра: без ack задание после повторных публикаций уходит в `dlq`, файл — `UPLOADED` → `ERROR`; строка `qa-guide.md` уточнена. Цена ошибки: нулевая.