From 157929596155faa5c79a8491c1f55a1a7ff906e4 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 12 Oct 2026 21:40:35 +0400 Subject: [PATCH] =?UTF-8?q?openspec:=20change=20nats-env-config=20?= =?UTF-8?q?=E2=80=94=20subject'=D1=8B=20=D0=B8=20JWT=20NATS=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=B8=D0=B7=20OpenBao?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../changes/nats-env-config/.openspec.yaml | 2 + openspec/changes/nats-env-config/design.md | 20 +++++++++ openspec/changes/nats-env-config/proposal.md | 40 ++++++++++++++++++ .../specs/image-recognition-gateway/spec.md | 42 +++++++++++++++++++ openspec/changes/nats-env-config/tasks.md | 18 ++++++++ 5 files changed, 122 insertions(+) create mode 100644 openspec/changes/nats-env-config/.openspec.yaml create mode 100644 openspec/changes/nats-env-config/design.md create mode 100644 openspec/changes/nats-env-config/proposal.md create mode 100644 openspec/changes/nats-env-config/specs/image-recognition-gateway/spec.md create mode 100644 openspec/changes/nats-env-config/tasks.md diff --git a/openspec/changes/nats-env-config/.openspec.yaml b/openspec/changes/nats-env-config/.openspec.yaml new file mode 100644 index 0000000..b9ebfc7 --- /dev/null +++ b/openspec/changes/nats-env-config/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-12 diff --git a/openspec/changes/nats-env-config/design.md b/openspec/changes/nats-env-config/design.md new file mode 100644 index 0000000..1a98b82 --- /dev/null +++ b/openspec/changes/nats-env-config/design.md @@ -0,0 +1,20 @@ +## Context + +Дизайн сквозной: `../docs/openspec/changes/nats-env-config/design.md` (решения 1–7, R-1…R-6). Здесь — только то, что +специфично для `scan-gateway`. + +## Decisions + +1. Сервис подписан на `image_request` (queue group `scan-gateway`), публикует ack и результат в `result`; в `/healthcheck.subjects` — `image_request`, `result`. +2. Проверяются все три subject'а, включая неиспользуемые сервисом (docs design, решение 1 и R-3). +3. Выбор subject'а и подписки получают имена из `settings.OPENBAO.NATS`; константы `contract/subjects.py` остаются + умолчаниями полей настроек. Форма передачи (аргумент, объект subject'ов) — в плане. + +## Risks / Trade-offs + +См. docs design: разъехавшиеся настройки — тишина на шине; JWT без прав на заданные subject'ы — ошибка прав NATS в логе. + +## Decisions made without discussion + +Решения без обсуждения ведутся в сквозном `../docs/openspec/changes/nats-env-config/design.md` (R-1…R-6); решения, +принятые при исполнении в этом репозитории, дописываются сюда с маркером `R-n` сквозной нумерации. diff --git a/openspec/changes/nats-env-config/proposal.md b/openspec/changes/nats-env-config/proposal.md new file mode 100644 index 0000000..26c9c8a --- /dev/null +++ b/openspec/changes/nats-env-config/proposal.md @@ -0,0 +1,40 @@ +## Why + +Прод и тест будут работать с разными наборами subject'ов NATS и разными JWT; платформа передаёт в под только +`OPENBAO__*`. Сейчас subject'ы — константы `api/src/contract/subjects.py`. Решение владельца 12.10.2026: subject'ы и +JWT задаются по окружению секретами группы `OPENBAO__NATS__`, как в `RSHB/armka_file_worker`. Сквозной change — +`../docs/openspec/changes/nats-env-config` (RSHB/arm-ka-docs#80); здесь — его часть для `scan-gateway` (RSHB/arm-scan-gateway#32). + +Срез: вне плана срезов `09-delivery-plan.md` — подготовка к контуру. Таблицы DBML v5.17 не затрагиваются +(`recognition_job.request_subject` по-прежнему хранит фактический subject публикации). + +## What Changes + +- `NATSSettings`: `NATS_TABULAR_REQUEST_SUBJECT` (`files.tabular_uploaded`), `NATS_IMAGE_REQUEST_SUBJECT` + (`files.img_uploaded`), `NATS_RESULT_SUBJECT` (`files.tabular_parsed`); проверка всех трёх при старте (непустые, без + пробелов, `*`, `>` и пустых токенов, попарно различны). +- `NATS_JWT`, `NATS_SEED` — `SecretStr`; `Settings(hide_input_in_errors=True)`. +- Сервис подписан на `image_request` (queue group `scan-gateway`), публикует ack и результат в `result` — по настройкам; константы `contract/subjects.py` — только умолчания. +- Лог при старте и `/healthcheck.subjects` (`image_request`, `result`), независимо от состояния NATS. +- Тесты интеграционные (`tests/integration/`) задают собственные subject'ы через настройки (OQ-83). +- `.env.example`, README. + +## Non-goals + +- `schema_version` контракта и копии схем не меняются (кроме побайтной синхронизации `description` из docs, если она + изменится). +- Queue group'ы — константы; механизм nkeys/JWT не меняется. +- Пароли MinIO/SMB в `SecretStr` не переводятся. +- Настройка через `OPENBAO__APP__*` не вводится. + +## Capabilities + +### New Capabilities + +### Modified Capabilities +- `image-recognition-gateway`: subject'ы обмена и учётные данные NATS — настройки окружения; `/healthcheck.subjects`. + +## Impact + +Файлы: `api/src/core/config.py`, `api/src/contract/subjects.py`, `api/src/core/nats.py`, `api/src/gateway/`, `api/src/core/health.py`, `api/src/app.py` (лог старта), тесты (`tests/unit/test_config*.py`, `tests/unit/test_nats.py`, `tests/unit/test_app.py`, `tests/integration/`), `api/.env.example`, README. Выкат обратно совместим (умолчания — текущие имена); набор окружения задаётся одинаково всем трём +сервисам. diff --git a/openspec/changes/nats-env-config/specs/image-recognition-gateway/spec.md b/openspec/changes/nats-env-config/specs/image-recognition-gateway/spec.md new file mode 100644 index 0000000..861be2a --- /dev/null +++ b/openspec/changes/nats-env-config/specs/image-recognition-gateway/spec.md @@ -0,0 +1,42 @@ +## ADDED Requirements + +### Requirement: Subject'ы и учётные данные NATS из настроек окружения +Сервис SHALL брать subject'ы обмена из `OPENBAO__NATS__NATS_TABULAR_REQUEST_SUBJECT`, `NATS_IMAGE_REQUEST_SUBJECT`, +`NATS_RESULT_SUBJECT` (умолчания `files.tabular_uploaded`, `files.img_uploaded`, `files.tabular_parsed`): подписан на `image_request` (queue group `scan-gateway`), публикует ack и результат в `result`. +При старте SHALL проверяться все три имени (непустое, без пробельных символов, без `*` и `>`, без пустых токенов, +попарно различны); при нарушении приложение MUST NOT стартовать. Значения `NATS_JWT` и `NATS_SEED` MUST NOT попадать в +логи, `repr` настроек и тексты ошибок валидации. Используемые subject'ы SHALL писаться в лог при старте и отдаваться в +`/healthcheck` объектом `subjects` с ключами `image_request`, `result` при любом значении `nats` (`ok | down | off`). + +#### Scenario: Умолчания +- **WHEN** приложение стартует без ключей `OPENBAO__NATS__NATS_*_SUBJECT` +- **THEN** `/healthcheck.subjects` содержит имена `files.*` по умолчанию, обмен идёт по ним + +#### Scenario: Subject'ы окружения +- **WHEN** заданы `OPENBAO__NATS__NATS_TABULAR_REQUEST_SUBJECT=test.files.tabular_uploaded`, + `OPENBAO__NATS__NATS_IMAGE_REQUEST_SUBJECT=test.files.img_uploaded`, + `OPENBAO__NATS__NATS_RESULT_SUBJECT=test.files.tabular_parsed` +- **THEN** подписки и публикации идут только по этим subject'ам, `/healthcheck.subjects` их показывает + +#### Scenario: Недопустимый subject +- **WHEN** `OPENBAO__NATS__NATS_RESULT_SUBJECT` совпадает с subject'ом запроса или содержит `>` +- **THEN** приложение не стартует, ошибка называет ключ + +#### Scenario: JWT не раскрывается +- **WHEN** заданы `NATS_JWT` и `NATS_SEED`, а валидация настроек падает на другом поле +- **THEN** текст ошибки и лог не содержат значений JWT и seed; `repr` настроек показывает их скрытыми + +#### Scenario: Тесты на своих subject'ах +- **WHEN** тесты интеграционные (`tests/integration/`) запускаются при работающих контейнерах стенда на том же NATS +- **THEN** тесты используют уникальные subject'ы из настроек и не получают и не забирают сообщения стенда + +## MODIFIED Requirements + +### Requirement: Соединение с NATS без JetStream +При пустых `NATS_SEED`/`NATS_JWT` сервис SHALL подключаться без учётных данных, иначе — с nkeys/JWT из `OPENBAO__NATS__NATS_SEED`/`NATS_JWT` (значения — секреты); MUST NOT требовать +stream'ов и consumer'ов JetStream; при разрыве SHALL переподключаться и восстанавливать подписку; `/healthcheck.nats` +SHALL отражать состояние. + +#### Scenario: NATS перезапущен +- **WHEN** контейнер NATS перезапущен во время работы сервиса +- **THEN** в течение 20 с после старта NATS подписка восстановлена, `nats = ok` diff --git a/openspec/changes/nats-env-config/tasks.md b/openspec/changes/nats-env-config/tasks.md new file mode 100644 index 0000000..743c337 --- /dev/null +++ b/openspec/changes/nats-env-config/tasks.md @@ -0,0 +1,18 @@ +## 1. Тесты + +- [ ] 1.1 Unit: умолчания subject'ов, чтение `OPENBAO__NATS__NATS_*_SUBJECT`, отказ на `*`/`>`/пробеле/пустом токене и + совпадающих именах; `SecretStr` для `NATS_JWT`/`NATS_SEED`, отсутствие значений в ошибке валидации и `repr` +- [ ] 1.2 Unit: `/healthcheck.subjects` (`image_request`, `result`) при `nats = ok | down | off`; лог старта с subject'ами +- [ ] 1.3 Тесты интеграционные (`tests/integration/`) — уникальные subject'ы через настройки + +## 2. Реализация + +- [ ] 2.1 `core/config.py`: поля и валидация `NATSSettings`, `SecretStr`, `hide_input_in_errors=True` +- [ ] 2.2 Колбэки nkeys — `get_secret_value()` +- [ ] 2.3 Подписка и публикация по настройкам; константы `contract/subjects.py` — умолчания +- [ ] 2.4 `/healthcheck.subjects`, лог при старте + +## 3. Документы + +- [ ] 3.1 `api/.env.example`, README (переменные NATS, `/healthcheck`) +- [ ] 3.2 `make check-full` зелёный