openspec: change nats-env-config — subject'ы и JWT NATS по окружению из OpenBao

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-12 21:40:35 +04:00
parent d49fa63e55
commit 1579295961
5 changed files with 122 additions and 0 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-12

View File

@ -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` сквозной нумерации.

View File

@ -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. Выкат обратно совместим (умолчания — текущие имена); набор окружения задаётся одинаково всем трём
сервисам.

View File

@ -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`

View File

@ -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` зелёный