Обмен с ядром на core NATS: подписка queue group, ack status=processing сразу по получении, process-local LRU job_id против повторных запросов, публикация результата с повторами; удалены durable consumer / max_deliver / ack_wait. Dockerfile python:3.11-slim; make check / make check-full вместо CI; raisa_* фильтруются при dev-установке. Обновлены proposal, design (решения без обсуждения), specs, tasks, CLAUDE.md, openspec/config.yaml. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
84 lines
8.7 KiB
Markdown
84 lines
8.7 KiB
Markdown
# Proposal: Срез 0 — скелет ядра arm-core
|
||
|
||
**Срез:** 0 «Скелет» (`../docs/context/09-delivery-plan.md`). **Репозиторий:** arm-core. **Issue:** RSHB/arm-core#1.
|
||
**Родительский change:** `../docs/openspec/changes/slice-0-skeleton` (контракт v1.0, compose, проверки качества).
|
||
**Соседние:** `../tabular-parser/openspec/changes/slice-0-skeleton`, `../scan-gateway/openspec/changes/slice-0-skeleton`.
|
||
|
||
## Why
|
||
|
||
Ядро — единственный писатель в PostgreSQL и единственная точка обмена с сервисами распознавания. Пока нет схемы БД по
|
||
DBML v5.13, прав как данных и работающего outbox/inbox, ни один следующий срез (справочники, загрузка файлов, парсер)
|
||
не может начаться: каждому нужны таблицы, пользователь с правами и канал в NATS. Срез 0 делает всё это один раз,
|
||
без бизнес-логики, и доказывает end-to-end тестом на пустых обработчиках.
|
||
|
||
## What Changes
|
||
|
||
- **Инструменты шаблона**: `api/pyproject.toml` (ruff — формат, совместимый с black, и lint; mypy), `requirements-dev.txt`
|
||
переписан как `-r requirements.txt` + dev-инструменты (пины шаблона были рассинхронизированы), `Dockerfile`
|
||
(`python:3.11-slim` — версия платформы; строка `Dockerfile` убрана из `.gitignore`), `Makefile` с общими целями
|
||
`up / test / test-integration / lint / check / check-full` и своими `venv / migrate / seed`, `.env.example`, `.env.test`;
|
||
`Settings` читает путь к env-файлу из `ENV_FILE` (шаблонный `pytest.ini` уже задаёт его, но `config.py` игнорировал).
|
||
Без Nexus: `make venv` ставит `requirements.txt` без строк `raisa_*`; в `web/package.json` `raisa-ui` и
|
||
`raisa-fastapi-protected-web` → `optionalDependencies`.
|
||
- **Модель данных**: ORM 1:1 с `../docs/model/data-model-v5.13.dbml` — 43 таблицы, 27 enum'ов, все FK, уникальные и
|
||
частичные индексы (`related_group_member` WHERE is_current, `consolidation_lock` WHERE released_at IS NULL,
|
||
`document` NULLS NOT DISTINCT), схема `dkb`. Миграция `0001_dbml_v5_13` создаёт всё с нуля и полностью откатывается.
|
||
`api/scripts/check_dbml_vs_orm.py` сравнивает DBML и ORM-метаданные; расхождение — ошибка CI.
|
||
- **Seed** (`api/seed/`, идемпотентный `python -m scripts.seed`): `regional_office` (ГО), `role` (7 ролей ТЗ 8.1),
|
||
`permission` (16 сочетаний объект × действие из §10), `role_permission` по §10 (ASSUMPTION(OQ-16)),
|
||
`chart_of_account` (План счетов 94н + субсчета типовой 1С, упомянутые в §4/§6), `elimination_rule` v1 для ББ (§4, 8 правил)
|
||
с `elimination_rule_account`, `required_document_rule` для BASIC и EXTENDED (ASSUMPTION(OQ-46)); `--dev`: примеры РФ и
|
||
по одному `app_user` на роль.
|
||
- **Аутентификация и права**: `core/auth.py` — проверка JWT (`TokenVerifier`: dev HS256-эмитент за флагом
|
||
`AUTH_DEV_ISSUER`; каркас `RaisaVerifier` под JWKS/публичный ключ, параметры — вопрос к человеку), поиск `app_user`
|
||
по email, зависимости `current_user` и `require(permission_code)`, `GET /api/v1/me`, `POST /api/v1/dev/token`
|
||
(только при включённом флаге). Policy-слой (филиал/ЗО/статус) — не здесь (Срез 1+).
|
||
- **Обмен через `recognition_job`** (DECISION-4, **NATS без JetStream**): вендоренные схемы контракта `api/contracts/` +
|
||
pydantic-модели `src/contract/`; outbox-паблишер (`queued` → NATS → `published`, `Nats-Msg-Id = <job_id>:<attempt>`);
|
||
подписка queue group `arm-core` на `recognition.result`: ack сервиса (`status = processing`) → `processing`/`started_at`,
|
||
результат → дедуп по `result_msg_id`, обновление статуса/метрик, регистрация файла результата в `s3_object`;
|
||
повторная публикация запроса без ack (`ACK_TIMEOUT_SECONDS` = 30, до `OUTBOX_MAX_REPUBLISH` = 3 раз) → затем `dlq`;
|
||
вахтёр `reply_deadline` → `timeout`. Маппинг документов — точка расширения, реализуется в Срезе 2.
|
||
- **Аудит**: `services/audit.py` — единственный способ записать `audit_event` (append-only, `before/after` jsonb).
|
||
- **Проверки качества** (CI в Gitea нет, пайплайн платформы вне репозитория): `make check` — ruff, mypy, pytest без
|
||
Docker, идентичность контракта; `make check-full` — плюс интеграционные тесты против compose, миграции
|
||
upgrade → downgrade → upgrade, `check_dbml_vs_orm`, сборка образа, `web`: biome, tsc, rsbuild build.
|
||
- **Документация репозитория**: `README.md` заменяется описанием сервиса (команды, env, как поднять), `CLAUDE.md` —
|
||
раздел «Соглашения шаблона» (уже в ветке).
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
- `data-model`: схема БД = DBML v5.13; миграции применяются с нуля и откатываются; сверка ORM ↔ DBML.
|
||
- `reference-data-seed`: идемпотентная загрузка справочников продукта (роли, права, матрица, план счетов, правила
|
||
элиминации, чек-лист) и dev-данных.
|
||
- `authn-authz`: вход по JWT, пользователь по email, эффективные права, зависимость `require(permission)`, `/api/v1/me`.
|
||
- `recognition-job-exchange`: outbox/inbox через `recognition_job`, идемпотентность, таймауты, DLQ, регистрация результата в `s3_object`.
|
||
- `audit-trail`: append-only запись событий `audit_event` через единый сервис.
|
||
|
||
### Modified Capabilities
|
||
<!-- openspec/specs пуст -->
|
||
|
||
## Impact
|
||
|
||
- **Таблицы DBML v5.13:** все 43 (ORM и миграция); данными заполняются `regional_office`, `role`, `permission`,
|
||
`role_permission`, `chart_of_account`, `elimination_rule`, `elimination_rule_account`, `required_document_rule`,
|
||
`app_user` (dev), `user_role` (dev); в рантайме Среза 0 изменяются `recognition_job`, `s3_object`, `audit_event`.
|
||
Новых полей и таблиц нет.
|
||
- **Параграфы правил:** §4 (seed правил элиминации), §9.4 (жизненный цикл `recognition_status`), §10 (матрица прав).
|
||
- **Зависимости (новые):** `PyJWT[crypto]`, `pydbml` (dev, сверка DBML), `ruff`, `mypy`, `pytest-cov`, `types-*`;
|
||
`nats-py` уже есть (core NATS, заголовки), `miniopy-async` уже есть.
|
||
- **Внешние системы:** NATS без JetStream (subjects `recognition.*`), MinIO/S3 (бакет `arm-ka`), PostgreSQL 16
|
||
(`CREATE SCHEMA dkb`, `CREATE TYPE`).
|
||
- **Фронт:** код не меняется; `raisa-*` в `package.json` → `optionalDependencies`; проверки `web` входят в `make check-full`.
|
||
|
||
## Non-goals
|
||
|
||
- Загрузка файлов, `stored_file`/`document` жизненный цикл и маппинг результата в `osv_entry`/`financial_statement_*` (Срез 2).
|
||
- CRUD справочников и экраны администратора (Срез 1); policy-слой по филиалу/ЗО/статусу (Срез 1+).
|
||
- Реальная проверка JWT РАИСА (нужны параметры — Open Questions родительского change'а); в Срезе 0 — только dev-эмитент
|
||
и конфигурируемый каркас.
|
||
- Авто-элиминация ОФР (OQ-09) — правила ОФР в seed не входят.
|
||
- Полный план счетов конкретного клиента — только стандартный 94н + типовые субсчета 1С; остальное добавляет администратор (ARCH-004).
|
||
- Любые экраны фронта.
|