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..c9c0f0c --- /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). Здесь — только то, что +специфично для `arm-core`. + +## Decisions + +1. Ядро публикует запросы в `tabular_request` и `image_request`, подписан на `result` (queue group `arm-core`); в `/healthcheck.subjects` — `tabular_request`, `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..906b882 --- /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); здесь — его часть для `arm-core` (RSHB/arm-core#106). + +Срез: вне плана срезов `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)`. +- Ядро публикует запросы в `tabular_request` и `image_request`, подписан на `result` (queue group `arm-core`) — по настройкам; константы `contract/subjects.py` — только умолчания. +- Лог при старте и `/healthcheck.subjects` (`tabular_request`, `image_request`, `result`), независимо от состояния NATS. +- e2e (`tests/e2e/`) остаётся на subject'ах по умолчанию: ему нужен контейнер `tabular-parser` стенда (docs design R-7). +- `.env.example`, README. + +## Non-goals + +- `schema_version` контракта и копии схем не меняются (кроме побайтной синхронизации `description` из docs, если она + изменится). +- Queue group'ы — константы; механизм nkeys/JWT не меняется. +- Пароли MinIO/SMB в `SecretStr` не переводятся. +- Настройка через `OPENBAO__APP__*` не вводится. + +## Capabilities + +### New Capabilities + +### Modified Capabilities +- `recognition-job-exchange`: subject'ы обмена и учётные данные NATS — настройки окружения; `/healthcheck.subjects`. + +## Impact + +Файлы: `api/src/core/config.py`, `api/src/contract/subjects.py`, `api/src/core/nats.py`, `api/src/services/recognition_exchange.py`, `api/src/services/primary_files.py`, `api/src/app.py` (`/healthcheck`, лог старта), тесты (`tests/test_config*.py`, `tests/test_nats_*.py`, `tests/e2e/`), `api/.env.example`, README. Выкат обратно совместим (умолчания — текущие имена); набор окружения задаётся одинаково всем трём +сервисам. diff --git a/openspec/changes/nats-env-config/specs/recognition-job-exchange/spec.md b/openspec/changes/nats-env-config/specs/recognition-job-exchange/spec.md new file mode 100644 index 0000000..772304e --- /dev/null +++ b/openspec/changes/nats-env-config/specs/recognition-job-exchange/spec.md @@ -0,0 +1,46 @@ +## 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`): публикует запросы в `tabular_request` и `image_request`, подписан на `result` (queue group `arm-core`). +При старте SHALL проверяться все три имени (непустое, без пробельных символов, без `*` и `>`, без пустых токенов, +попарно различны); при нарушении приложение MUST NOT стартовать. Значения `NATS_JWT` и `NATS_SEED` MUST NOT попадать в +логи, `repr` настроек и тексты ошибок валидации. Используемые subject'ы SHALL писаться в лог при старте и отдаваться в +`/healthcheck` объектом `subjects` с ключами `tabular_request`, `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` настроек показывает их скрытыми + +## MODIFIED Requirements + +### Requirement: Соединение с NATS по шаблону, без JetStream +Ядро SHALL подключаться к NATS с nkeys-seed и JWT, если заданы `OPENBAO__NATS__NATS_SEED/NATS_JWT`, и без учётных данных, +если они пусты; подписываться на subject ответа `NATS_RESULT_SUBJECT` (по умолчанию `files.tabular_parsed`) в queue group `arm-core`; переподключаться автоматически. +Stream'ы и consumer'ы JetStream MUST NOT требоваться. Фоновые задачи SHALL запускаться при `NATS_ENABLED=true`. + +#### Scenario: Dev-окружение compose +- **WHEN** ядро стартует с `DEBUG=true`, `NATS_ENABLED=true` и пустыми учётными данными против NATS без JetStream +- **THEN** подписка активна, `/healthcheck` отдаёт `nats = ok`, в логах нет ошибок подключения + +#### Scenario: Два инстанса ядра +- **WHEN** запущены два инстанса с `NATS_ENABLED=true` и опубликован один результат +- **THEN** результат обработан ровно одним инстансом (queue group), строка задания изменена один раз + +#### Scenario: NATS выключен +- **WHEN** `NATS_ENABLED=false` +- **THEN** приложение стартует, фоновые задачи не запускаются, `/healthcheck` отвечает 200 с `nats = off` diff --git a/openspec/changes/nats-env-config/tasks.md b/openspec/changes/nats-env-config/tasks.md new file mode 100644 index 0000000..de04193 --- /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` (`tabular_request`, `image_request`, `result`) при `nats = ok | down | off`; лог старта с subject'ами +- [ ] 1.3 Интеграционный тест: приложение с 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` зелёный