Andrey Epifancev 3a3167441d qa-guide: симптом разъехавшихся subject'ов уточнён (T8.1); design R-15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:40:35 +04:00

112 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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