I-1: предупреждение dev-эмитента появляется при первом запросе к /me, а не при старте. I-2: добавлен трейсбэк SMB (заглушка). S3-трейсбэк помечен «до исправления в задаче 22». M-3: дождаться healthy перед make migrate. M-5: таблица env с полными именами OPENBAO__MINIO__*, __NATS__*, __SMB__*. M-2: migration-cycle и check-full читают .env.test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
25 KiB
arm-core — ядро АРМ КА
Ядро автоматизированного рабочего места кредитного аналитика Департамента крупного бизнеса (АРМ КА, АО «Россельхозбанк»):
справочники, файлы и документы, приём результатов распознавания, вычисления консолидации (КФО ГСК), маршрут
согласования, Единое хранилище (ЕХ), аудит и права. Единственный писатель в PostgreSQL: сервисы распознавания
(tabular-parser, scan-gateway) видят только NATS и S3.
Монорепозиторий по шаблону команды «Шаблон FastAPI»: api/ — бэкенд (FastAPI, SQLAlchemy 2 async, Alembic,
PostgreSQL 16, core NATS без JetStream), web/ — фронтенд (React + TypeScript, rsbuild).
Этот репозиторий — часть workspace АРМ КА. Общий контекст, модель данных, правила предметной области и контракты — в соседних репозиториях:
../CLAUDE.md— инварианты и процесс работы всего workspace;../docs/— контекст (context/), модель данных (model/), контракты (contracts/), окружение (dev/);CLAUDE.md— соглашения этого репозитория.
Срез 0 («скелет ядра»): схема БД по DBML v5.13, справочники (seed), аутентификация и права, GET /api/v1/me,
/healthcheck. Спецификация — openspec/changes/slice-0-skeleton/.
Предварительные требования
- Python 3.11 (версия платформы) и
uv—make venvсоздаёт окружение и ставит зависимости черезuv; без него цель не работает. Системныйpython3другой версии не подойдёт:uvсам подберёт интерпретатор 3.11. - Docker и compose v2 — для PostgreSQL, NATS и MinIO (compose лежит в
../docs/dev/). - Node.js и
npm— только дляweb/. Для запуска бэкенда не нужны. make,curl.
Репозитории workspace должны лежать рядом: arm-core/ и docs/ — соседние каталоги (так же устроено зеркало
.wt/<срез>/). Пути ../docs/... ниже и в Makefile рассчитаны на это.
Быстрый старт
Путь «с нуля до зелёного /api/v1/me». Команды make выполняются из корня репозитория.
1. Виртуальное окружение
make venv
Создаёт api/.venv (Python 3.11) и ставит api/requirements.txt и api/requirements-dev.txt. Пакеты raisa_*
из Nexus банка вне банка недоступны, поэтому цель пропускает строки raisa_* (подробнее — «Работа без Nexus»).
Повторный запуск безопасен.
2. Инфраструктура (PostgreSQL, NATS, MinIO)
Файл .env compose — отдельный от api/.env; он создаётся один раз и в git не попадает:
cp ../docs/dev/.env.example ../docs/dev/.env
make up # docker compose -f ../docs/dev/docker-compose.yml up -d
make up поднимает postgres (5432), nats (4222, мониторинг 8222), minio (9000, консоль 9001) и одноразовый
minio-init, который создаёт бакет arm-ka и завершается с кодом 0. Команда возвращается сразу, а PostgreSQL становится
готовым примерно через 6 секунд: дождитесь healthy у трёх долгоживущих сервисов, прежде чем выполнять make migrate:
docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio # возвращается, когда все healthy
docker compose -f ../docs/dev/docker-compose.yml ps # проверка статуса
(голый --wait без списка сервисов возвращает код 1 из-за завершающегося minio-init; поэтому make up для бакета
и --wait со списком — вместе). Подробности, смена портов, сброс — ../docs/dev/README.md.
3. Настройки приложения
cp api/.env.example api/.env
Значения из api/.env.example уже совпадают с ../docs/dev/.env.example (стандартные порты и учётные данные compose),
менять ничего не нужно. Менять api/.env придётся, только если вы изменили порты или пароли в ../docs/dev/.env
(правьте обе части). api/.env в git не попадает.
4. Миграции и справочники
make migrate # alembic upgrade head: схема dkb, 27 enum-типов, 43 таблицы
make seed-dev # справочники продукта + dev-пользователи (по одному на каждую роль)
make seed загружает только справочники (роли, права, филиалы, план счетов, правила элиминации, чек-лист), без
dev-пользователей. Seed идемпотентен: повторный запуск обновляет данные и ничего не дублирует.
5. Запуск
cd api && .venv/bin/python run_server.py
Сервер слушает 0.0.0.0:8000 (SERVER__HOST, SERVER__PORT). Дальнейшие команды — из другого терминала.
6. Проверка
curl -s localhost:8000/healthcheck
{"uptime":"2026-10-03T18:37:15.121394+00:00","version":"1.0.0","nats":"ok"}
nats: ok — подключён, down — включён (NATS_ENABLED=true), но связи нет, off — выключен.
Dev-токен по email любого dev-пользователя и профиль текущего пользователя:
curl -s -X POST localhost:8000/api/v1/dev/token \
-H 'Content-Type: application/json' \
-d '{"email":"ka_rf@armka.local"}'
# {"access_token":"<JWT>","token_type":"bearer"}
curl -s localhost:8000/api/v1/me -H "Authorization: Bearer <JWT>"
{
"id": "b8d7595a-787f-4bcb-8e93-7612cc8afba3",
"email": "ka_rf@armka.local",
"full_name": "Dev: Аналитик РФ",
"regional_office": {
"id": "3b38e823-e09a-47ce-a296-061948a200b0",
"name": "Ростовский региональный филиал",
"is_head_office": false
},
"business_segment": "DKB",
"roles": ["KA_RF"],
"permissions": [
"CONSOLIDATION_EDIT", "CONSOLIDATION_VIEW", "DIRECTORY_EDIT", "DIRECTORY_VIEW", "DOCUMENT_EDIT",
"DOCUMENT_VIEW", "EXPORT", "EX_VIEW", "KFO_EXPORT", "KFO_VIEW"
]
}
(идентификаторы в вашей БД будут другими). Токен действует 8 часов. Интерактивная документация API — /docs
(Swagger UI), схема — /openapi.json.
Dev-пользователи (api/seed/dev/users.yaml, создаются make seed-dev):
| Роль | Филиал | |
|---|---|---|
ka_rf@armka.local |
KA_RF |
Ростовский региональный филиал |
rka@armka.local |
RKA |
Ростовский региональный филиал |
client_manager@armka.local |
CLIENT_MANAGER |
Краснодарский региональный филиал |
ka_go@armka.local |
KA_GO |
Головной офис |
curator_go@armka.local |
CURATOR_GO |
Головной офис |
expert@armka.local |
EXPERT |
Головной офис |
admin@armka.local |
ADMIN |
Головной офис |
Права не зашиты в код роли: effective_permissions(user) — объединение прав ролей пользователя (таблицы permission,
role_permission).
Что вы увидите в логе при запуске
Старт занимает около секунды; в логе два трейсбэка — это ожидаемо в dev, приложение при этом работает:
- Трейсбэк SMB:
Ошибка при регистрации сессии SMB ... Failed to connect to 'localhost:445': Connection refused. НастройкиOPENBAO__SMB__*— заглушки, SMB в Срезе 0 не используется; игнорируйте. Пока наlocalhost:445ничего не слушает, отказ приходит сразу (старт не задерживается); если порт отвечает, но молчит, ожидание ограничено тайм-аутомsmbclient(60 с). - Трейсбэк S3:
ClientConnectorSSLError ... localhost:9000. Клиент S3 из шаблона ходит поhttps, а MinIO из compose слушаетhttp. S3 в Срезе 0 не используется (app.state.storageостаётся пустым). Это до исправления в задаче 22 (отдельная настройка схемы подключения к MinIO).
Недоступность SMB, S3 или NATS при старте приложение не роняет: ошибка логируется, состояние NATS видно в
/healthcheck.
Предупреждение «Включён dev-эмитент токенов … только для разработки» появляется не при старте, а при первом
запросе к защищённому эндпоинту (/api/v1/me): проверяющий токены создаётся лениво (штатно, см. «Безопасность
dev-режима»).
Остановка и сброс
docker compose -f ../docs/dev/docker-compose.yml down # остановить, данные (тома) сохраняются
docker compose -f ../docs/dev/docker-compose.yml down -v # полный сброс: БД и бакет с нуля (затем снова migrate и seed-dev)
Имя проекта compose фиксировано (arm-ka), тома общие для всех worktree.
Конфигурация
Настройки — pydantic-settings; читаются из переменных окружения и файла api/.env. Вложенные настройки
разделяются __. Файл окружения выбирается переменной ENV_FILE (по умолчанию .env, путь относительно текущего
каталога — api/; значение читается при импорте). Тесты используют api/.env.test (pytest.ini).
Полный пример — api/.env.example.
| Переменная | По умолчанию | Назначение |
|---|---|---|
ENV_FILE |
.env |
файл окружения (относительно api/) |
DEBUG |
false |
режим разработки. true необходим dev-эмитенту токенов; при false пакеты РАИСА подгружаются условно |
SERVER__HOST, SERVER__PORT |
0.0.0.0, 8000 |
адрес и порт сервера (run_server.py) |
ROOT_PATH |
/ |
корневой путь за прокси (должен совпадать с REACT_APP_ROOT_PATH фронта) |
VERSION |
1.0.0 |
версия в /healthcheck и OpenAPI |
OPENBAO__DATABASE__DATABASE_URL |
— (обязательно) | PostgreSQL, драйвер asyncpg: postgresql+asyncpg://armka:armka@localhost:5432/armka |
OPENBAO__MINIO__MINIO_URL |
— (обязательно) | адрес MinIO без схемы (host:port, так ждёт miniopy-async) |
OPENBAO__MINIO__MINIO_ADMIN_LOGIN |
— (обязательно) | логин MinIO |
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD |
— (обязательно) | пароль MinIO |
OPENBAO__MINIO__MINIO_BUCKET |
— (обязательно) | бакет MinIO (arm-ka) |
OPENBAO__NATS__NATS_URL |
— (обязательно) | адрес NATS (nats://localhost:4222) |
OPENBAO__NATS__NATS_SEED |
пусто | nkey seed; пусто — подключение без учётных данных (compose, тесты). Задаётся вместе с NATS_JWT или не задаётся вовсе |
OPENBAO__NATS__NATS_JWT |
пусто | JWT NATS; см. NATS_SEED |
OPENBAO__SMB__SMB_HOST |
— (обязательно) | заглушка SMB (в Срезе 0 не используется, блок обязателен) |
OPENBAO__SMB__SMB_PATH |
— (обязательно) | заглушка SMB |
OPENBAO__SMB__SMB_LOGIN |
— (обязательно) | заглушка SMB |
OPENBAO__SMB__SMB_PASSWORD |
— (обязательно) | заглушка SMB |
NATS_ENABLED |
false |
подключение к NATS и обмен через recognition_job; без него /healthcheck показывает nats=off |
OUTBOX_POLL_SECONDS |
5 |
период опроса outbox |
ACK_TIMEOUT_SECONDS |
30 |
ожидание ack сервиса распознавания до повторной публикации |
OUTBOX_MAX_REPUBLISH |
3 |
число повторных публикаций до перевода в dlq |
AUTH_DEV_ISSUER |
false |
dev-эмитент токенов: работает только при AUTH_DEV_ISSUER=true и DEBUG=true |
AUTH_DEV_SECRET |
пусто | секрет HS256 dev-эмитента (не короче 32 байт); только для разработки |
AUTH_JWKS_URL или AUTH_PUBLIC_KEY |
пусто | проверка токенов РАИСА: JWKS по URL либо публичный ключ (PEM) |
AUTH_ISSUER, AUTH_AUDIENCE |
пусто | проверка iss и aud токена РАИСА |
AUTH_EMAIL_CLAIM |
email |
claim токена с email пользователя (сопоставляется с app_user.email) |
Параметры JWT РАИСА (алгоритм, claim, эмитент) пока неизвестны — это открытый вопрос
(../docs/context/07-open-questions.md); AUTH_JWKS_URL/AUTH_PUBLIC_KEY
и связанные поля — каркас под него. Если не настроено ни dev-эмитента, ни ключей РАИСА, токены не принимаются вовсе
(/me отвечает 401).
Порты по умолчанию: приложение 8000; compose — PostgreSQL 5432, NATS 4222 (мониторинг 8222), MinIO 9000 (консоль 9001).
Порты compose меняются в ../docs/dev/.env, тогда же — соответствующие OPENBAO__* в api/.env.
Команды make
Все цели выполняются из корня репозитория (сами переходят в api/).
| Цель | Что делает |
|---|---|
make venv |
api/.venv на Python 3.11 через uv, зависимости без raisa_* |
make up |
поднимает compose из ../docs/dev (PostgreSQL, NATS, MinIO, бакет) |
make migrate |
alembic upgrade head на БД из api/.env |
make seed |
загрузка справочников продукта (идемпотентна) |
make seed-dev |
то же и dev-пользователи/филиалы (--dev) — только для разработки |
make lint |
ruff check, ruff format --check, mypy src scripts |
make test |
юнит-тесты: pytest -m "not integration", Docker не нужен |
make test-integration |
интеграционные тесты: pytest -m integration, нужны PostgreSQL (и NATS) |
make dbml-check |
сверка ORM с ../docs/model/data-model-v5.13.dbml |
make migration-cycle |
миграции на временной БД: upgrade head → downgrade base → upgrade head, пустой autogenerate |
make check |
быстрая проверка без Docker: lint + test |
make check-full |
check + dbml-check + test-integration; выполняется перед PR (нужна поднятая инфраструктура) |
make check-full пока не включает проверки фронта, OpenAPI и откат миграций отдельным шагом (задача 25 среза).
Тесты
- Юнит-тесты (
make test) не обращаются ни к БД, ни к NATS, Docker не нужен. - Интеграционные (
make test-integration) требуют PostgreSQL, тесты обмена через NATS — ещё и NATS. Рабочую БДarmkaони не трогают: на каждую сессию pytest создаётся свежая БДarmcore_pytest_<hex>, мигрируетсяalembicи удаляется в конце. Параметры подключения берутся изapi/.env.test(pytest.iniзадаётENV_FILE=.env.test). Сейчас этот файл указывает на временную тестовую инфраструктуру (PostgreSQL55432, NATS54222, MinIO59000), а не на стандартные порты compose: чтобы гонять интеграционные тесты противmake up, поправьте порты вapi/.env.testна5432,4222,9000(не коммитьте это изменение без согласования). То же относится кmake migration-cycleиmake check-full: они тоже читают.env.testчерезpytest.ini. - Реальные данные — репозиторий
../fixtures/(путь можно задать переменнойARM_KA_FIXTURES). Тесты с маркеромfixturesбез каталога пропускаются с понятной причиной.
Проверка ORM и DBML
Модель данных — ../docs/model/data-model-v5.13.dbml. ORM обязана совпадать с ней один в один:
make dbml-check # пустой вывод и код 0 — совпадение; расхождения печатаются построчно
Другой путь к DBML: make dbml-check DBML=<путь относительно api/>.
Миграции
make migrate # применить: alembic upgrade head
cd api && .venv/bin/alembic downgrade base # откатить до пустой БД
make migration-cycle # проверить цикл upgrade -> downgrade -> upgrade на временной БД
Ревизии лежат в api/alembic/versions/; автогенерация — только черновик, финал сверяется с DBML (make dbml-check).
Раскладка репозитория
api/
run_server.py точка входа (uvicorn; хост и порт — из настроек)
alembic/ миграции (versions/0001_dbml_v5_13.py — вся модель v5.13)
seed/core/ справочники продукта (роли, права, филиалы, план счетов, правила элиминации, чек-лист)
seed/dev/ dev-данные: филиалы и пользователи по ролям
scripts/ check_dbml_vs_orm.py (ORM <-> DBML), seed.py и seed_lib/ (загрузчик справочников)
src/
app.py FastAPI-приложение: lifespan (S3, NATS, SMB), /healthcheck, раздача SPA
api/endpoints/ эндпоинты (me.py, dev_auth.py); router.py собирает /api/v1
api/dependencies/ зависимости FastAPI (current_user, проверка прав)
core/ config, auth (JWT), clients (S3), nats_callback, logger, exception_handlers
db/ движок и сессия (схема dkb)
models/ ORM-модели, 1:1 с DBML
repositories/ доступ к данным
schemas/ pydantic-схемы запросов и ответов
services/ бизнес-логика
common/ исключения
domain/ (появится) чистые функции по §§ правил предметной области, без ORM
tests/ pytest (юнит и integration/fixtures по маркерам)
web/ фронтенд (React 18 + TypeScript, rsbuild, antd); сборка web/dist -> api/web/
setup/ бутстрап шаблона через Nexus банка (pip.conf, setup.sh)
openspec/ спецификации и изменения (OpenSpec)
docs/superpowers/ планы реализации
raisa_config.yml ресурсы для платформы
Dockerfile только для локального compose
Слои: src/api/endpoints → services → repositories → models.
Работа без Nexus
Пакеты raisa_* (Python) и raisa-* (npm) лежат в Nexus банка и вне банка недоступны. Что с этим сделано:
make venvставитrequirements.txtбез строкraisa_*(фильтрgrep -v '^raisa_') и поэтому требуетuv, а неsetup/setup.sh(тот рассчитан наpip.confс Nexus).- Код импортирует
raisa_*условно: пакет middleware РАИСА импортируется только приDEBUG=false, иImportErrorдопустим — приложение работает без него. Аутентификация Среза 0 — собственная проверка JWT (api/src/core/auth.py). - В
web/package.jsonпакетыraisa-*вынесены вoptionalDependencies:npm installне падает, если Nexus недоступен. Файл.npmrcс адресом реестра (см.setup/setup.sh) вне банка не нужен.
Безопасность dev-режима
POST /api/v1/dev/tokenвыдаёт токен любому, кто знает email существующего активного пользователя: пароля нет. Только для локальной разработки и тестовых стендов.- Маршрут существует лишь при
AUTH_DEV_ISSUER=trueиDEBUG=true; при любом другом сочетании его нет вовсе (404), а dev-проверяющий токенов не создаётся даже при заданномAUTH_DEV_SECRET. В продеDEBUG=false, поэтому dev-эмитент там невозможен. AUTH_DEV_SECRETизapi/.env.example— публичный пример, в проде не использовать.make seed-devв прод не запускать.- Порты compose по умолчанию публикуются только на
127.0.0.1(BIND_ADDR); паролиarmka/minioadmin— только dev.
Соседние документы
CLAUDE.md— соглашения этого репозитория;../CLAUDE.md— workspace: инварианты, источники истины, процесс;../docs/dev/README.md— окружение разработки (compose), порты, типичные проблемы;../docs/context/03-architecture.md— архитектура, соглашения шаблона, NATS и S3;../docs/context/04-data-model-guide.mdи../docs/model/data-model-v5.13.dbml— модель данных;../docs/context/05-domain-rules.md— алгоритмы предметной области;../docs/context/07-open-questions.md— открытые вопросы;../docs/context/09-delivery-plan.md— план срезов;../docs/contracts/— JSON-схемы обмена с сервисами распознавания;openspec/changes/slice-0-skeleton/— спецификация и решения Среза 0.