Andrey Epifancev 122037eb1d openspec: архив nats-env-config (#80)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:56:38 +04:00

14 KiB
Raw Blame History

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 уточнена. Цена ошибки: нулевая.