arm-ka-docs/dev/contour-transfer-guide.md

217 lines
22 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.

# Перенос сервисов АРМ КА в контур банка: гайд для разработчика
Разработка ведётся **снаружи** (workspace `awp-ca`, Gitea проекта). В контур банка (GitLab платформы РАИСА и его CI/CD;
Gitea в контуре нет)
уезжают только три сервисных репозитория — `arm-core`, `tabular-parser`, `scan-gateway` — **снимками дерева, без
истории**. Репозитории `docs` и `fixtures` в контур не переносятся: решение владельца 06.10.2026. Этот документ — что
именно уезжает, чего в платформе нет, как подготовить снимок скриптом и как его положить в контур.
Соглашения платформы, от которых всё отталкивается, — `context/03-architecture.md`, разделы «Соглашения шаблона команды»
и «Что ожидаем от платформы». Проверки качества — `dev/checks.md`.
## 1. Что переносится и что нет
| Что | В контур | Почему |
|---|---|---|
| `api/` (код, тесты, `contracts/`, `rules/`, `alembic/`, `scripts/`), `web/` (arm-core), `README.md`, `Makefile`, `setup/`, `raisa_config.yml`, `api/.env.example`, `api/.env.test` | да | это и есть сервис по шаблону платформы |
| `openspec/specs/`, `openspec/changes/archive/` | да | спецификация системы и журнал решений (`design.md`); без них код в контуре нечем объяснять |
| `.claude/`, `CLAUDE.md`, `openspec/config.yaml` | **нет** | инструкции и контекст AI-агента (в `config.yaml` — блоки `context`/`rules` с путями `../docs`, `../fixtures`), инструментарий внешней разработки; `openspec` CLI в контуре нет, `specs/` и `design.md` читаются без него |
| `docs/superpowers/` (планы, ledger) | **нет** | рабочие артефакты процесса, содержат локальные пути разработчика |
| `Dockerfile`, `.dockerignore` | **нет** | образ для локального `make check-full`; в контуре образ собирает платформа своим CI (шаблон держит `Dockerfile` в `.gitignore`) |
| История коммитов | **нет** | снимок одним коммитом; в истории снаружи — трейлеры `Co-Authored-By: Claude …`, `Claude-Session`, ссылки на внешний Gitea |
| Репозиторий `docs` | **нет** | контекст, модель, контракты, экспорт ТЗ — остаются снаружи; копия контракта лежит в `api/contracts/` каждого сервиса |
| Репозиторий `fixtures` | **нет** | реальная отчётность для регрессии парсера; тесты с маркером `fixtures` в контуре пропускаются |
| `reference-old-arm` | **нет** | старый репозиторий банка, читался только для справки |
Снимок получает файл `SNAPSHOT.md` с хешем коммита-источника и датой: по нему потом сверяется, что уже перенесено.
## 2. Чего в платформе нет и что есть
**Нет в контуре (всё это скрипт вырезает или заменяет):**
- `uv` — `make venv` снаружи ставит окружение через `uv venv --python 3.11` и `uv pip`. В снимке цель `venv` переписана
на `python3.11 -m venv` + `pip` с `setup/pip.conf` (Nexus), как делает `setup/setup.sh` шаблона.
- Docker и `docker compose` — локальная инфраструктура (`docs/dev/docker-compose.yml`) живёт в `docs`, которого в контуре
нет. Цели `make up`, `test-integration`, `check-full` в контуре не запускаются; `docker-build` удалена из `Makefile`.
- `docs` и `fixtures` рядом с сервисом — тесты идентичности контракта и тесты на реальных данных пропускаются
(раздел 5), сверка ORM с DBML (`make dbml-check` в arm-core) не выполняется.
- `tea`, `codegraph`, `openspec` (CLI), хуки `.claude/hooks` — инструментарий процесса; его конфигурация
(`.claude/`, `CLAUDE.md`, `openspec/config.yaml`) вырезается.
- Внешний Gitea `git.aepif.ru` — ссылки вида `https://git.aepif.ru/RSHB/arm-core/pulls/19` в архивах change'ей
скрипт превращает в `RSHB/arm-core#19` (номера относятся к внешнему Gitea, в контуре их нет).
**Есть только в контуре (снаружи заменено заглушками, в снимке остаётся как есть):**
- Nexus `nexus.dev.raisa.go.rshbank.ru` (`setup/pip.conf`, `setup/.npmrc`) и пакеты `raisa_fastapi_protected_api`
(`api/requirements.txt` arm-core), `raisa-fastapi-protected-web`, `raisa-ui` (`web/package.json`, `optionalDependencies`).
Снаружи `make venv` их пропускает (`grep -v '^raisa_'`), импорт `raisa_fastapi_protected_api` условный — только при
`DEBUG=false` (в контуре временно `DEBUG=true`, см. 6.3). В контуре всё ставится штатно.
- Запуск платформой: `api/run_server.py`, `SERVER__HOST`/`SERVER__PORT`, ресурсы `raisa_config.yml`, секреты и настройки `OPENBAO__*`
из OpenBao (раздел 6), Python 3.11 в базовом образе.
- PostgreSQL 16 (схема `dkb`), NATS без JetStream (nkeys/JWT), S3-совместимое хранилище, вход временно через dev-заглушку, позже JWT РАИСА (раздел 6.3).
## 3. Подготовка снимка: `scripts/prepare-contour-snapshot.sh`
Запуск из workspace, по одному репозиторию:
```bash
docs/scripts/prepare-contour-snapshot.sh arm-core /tmp/contour
docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour
docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
# третий аргумент — ref; по умолчанию origin/main (снимок всегда с сервера, не из рабочего дерева)
```
Что делает скрипт (репозиторий-источник не меняется):
1. `git archive <ref>` → `/tmp/contour/<repo>/` — дерево коммита без `.git` и без игнорируемых файлов.
2. Удаляет `.claude/`, `CLAUDE.md`, `openspec/config.yaml`, `docs/superpowers/`, `Dockerfile`, `.dockerignore`.
3. Правит `Makefile`: цель `venv` → `python3.11 -m venv` + `pip install -r requirements-dev.txt` с `setup/pip.conf`
(переменная `PYTHON`, по умолчанию `python3.11`); удаляет цель `docker-build` и её упоминания в `check-full`, `.PHONY`,
`help`.
4. Заменяет ссылки на внешний Gitea на `RSHB/<repo>#<n>`.
5. Пишет `SNAPSHOT.md` (источник, хеш, дата, что вырезано).
6. Проверяет: нет `.claude`/`CLAUDE.md`/`openspec/config.yaml`/планов/`Dockerfile`, нет локальных путей (`/Users/…`, `/home/…`), нет `uv` и
`git.aepif.ru`, на месте платформенные файлы (`raisa_config.yml`, `setup/`, `run_server.py`, `requirements.txt`,
`.env.test`, `contracts/`). Любая проблема — код возврата 1 и список.
Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без
`docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5).
## 4. Как положить снимок в контур
Репозитории контура — проекты GitLab, по одному на сервис, имена как снаружи. Первый перенос и каждый следующий делаются
одинаково (вместо PR в GitLab — merge request, в остальном git тот же):
```bash
git clone <url контура>/arm-core contour-arm-core
cd contour-arm-core && git switch -c sync/arm-core-1d7f2c8 # ветка по правилам контура
rsync -a --delete --exclude .git /tmp/contour/arm-core/ ./ # дерево = снимок, удалённые снаружи файлы исчезают
git status --short # прочитать diff: он должен совпадать с изменениями снаружи
git add -A
git commit -m "sync: arm-core@1d7f2c8 (2026-10-06)" # хеш и дата — из SNAPSHOT.md
git push -u origin sync/arm-core-1d7f2c8 # дальше merge request и мерж по регламенту GitLab контура
```
`--delete` обязателен: иначе файлы, удалённые или переименованные снаружи, останутся в контуре. Если в контуре делали
локальные правки (раздел 7), сначала вернуть их наружу, иначе `rsync --delete` их перезапишет.
Порядок сервисов при одной синхронизации: `arm-core` → `tabular-parser` → `scan-gateway` (ядро — потребитель контракта,
его копия схемы должна быть не старше копий сервисов). Если снаружи поднималась версия контракта (`api/contracts/*.json`,
`CONTRACT_VERSION` в `api/src/contract/common.py`), переносить все три сервиса одним заходом.
## 5. Проверка в контуре перед merge request
```bash
make venv # python3.11 -m venv + pip через Nexus (вместо setup/setup.sh; тот ставит только прод-зависимости)
make check # ruff, mypy, pytest без интеграционных тестов
```
Ожидаемые пропуски (`pytest -rs` покажет причины), это **не** ошибки:
| Сервис | Пропускается | Причина |
|---|---|---|
| все | тест идентичности `api/contracts/` с `docs/contracts/` | `docs` нет; идентичность проверена снаружи перед снимком |
| все | тесты с маркером `fixtures` (golden-снимки, property-тесты на реальных ОСВ/формах, покрытие словарей) | `fixtures` нет; регрессия на реальных данных — снаружи |
| arm-core | сверка ORM с DBML в тестах | DBML в `docs` |
| scan-gateway | тесты с реальными ответами ИС Сканирование (`fixtures/ocr`) | то же |
Не запускать в контуре: `make up`, `make test-integration`, `make check-full`, `make dbml-check`, `make e2e` — им нужны
compose из `docs` либо Docker. Интеграционные тесты против PostgreSQL/NATS/MinIO прогоняются снаружи перед каждым снимком;
пайплайну платформы отдаётся `make check`.
Фронт arm-core: `make web-check` (`npm install` через `.npmrc` Nexus, biome, tsc, `npm run build`). В контуре
`raisa-fastapi-protected-web` и `raisa-ui` ставятся штатно, снаружи они в `optionalDependencies` и пропускаются.
`web/package-lock.json` снаружи не коммитится (OQ-63) — в контуре lock-файл появится при первом `npm install`, решение о его
коммите за контуром.
## 6. Что нужно от платформы для запуска
### 6.1. Как задаются настройки: только через OpenBao
Платформа РАИСА передаёт в под **только секреты OpenBao**; обычные переменные окружения (`DEBUG`, `NATS_ENABLED`, ...)
задать нельзя (ответ разработчика переноса, 07.10.2026). Поэтому любая настройка верхнего уровня `Settings` задаётся
как **`OPENBAO__APP__<ИМЯ>`** (arm-core PR #49, issue #44; arm-scan-gateway PR #12, issue #11; arm-tabular-parser PR #23,
issue #22; файл `api/src/core/settings_sources.py` одинаков в трёх сервисах). Правила:
- приоритет источников: обычная переменная окружения → `OPENBAO__APP__*` → `api/.env` → значение по умолчанию;
- `OPENBAO__APP__*` читается только из окружения процесса, в `.env` не действует;
- имя должно существовать в `Settings`; неизвестное имя — ошибка при старте (опечатка не остаётся незамеченной);
- вложенные настройки — через `__`: `OPENBAO__APP__SERVER__PORT`;
- `OPENBAO__APP__OPENBAO__...` запрещено: секреты задаются только как `OPENBAO__<ГРУППА>__<ИМЯ>`.
Имена и смысл настроек — `api/.env.example` каждого сервиса. Секреты (общие для всех сервисов: `OPENBAO__MINIO__*`,
`OPENBAO__NATS__*`) задаются как раньше.
### 6.2. Значения контура по сервисам
| Сервис | Секреты `OPENBAO__<ГРУППА>__*` | Настройки `OPENBAO__APP__*` |
|---|---|---|
| arm-core | `OPENBAO__DATABASE__DATABASE_URL` (asyncpg, схема `dkb`), `OPENBAO__MINIO__{MINIO_URL, MINIO_ADMIN_LOGIN, MINIO_ADMIN_PASSWORD, MINIO_BUCKET}` (`MINIO_SECURE=true` по умолчанию), `OPENBAO__NATS__{NATS_URL, NATS_SEED, NATS_JWT}` | `NATS_ENABLED=true` (по умолчанию `false`: без него NATS не подключается и обмен с сервисами распознавания не работает), `DEBUG=true`, `AUTH_DEV_ISSUER=true`, `AUTH_DEV_SECRET=<случайная строка от 32 байт>` (поле верхнего уровня `Settings`, поэтому тоже через `OPENBAO__APP__`), `FORWARDED_ALLOW_IPS=<адреса прокси платформы>` (иначе IP в аудите — адрес прокси); `CORS_ORIGINS` при `DEBUG=true` не нужен. `SERVER__HOST` не задавать (платформа задаёт сама) |
| scan-gateway | `OPENBAO__MINIO__*`, `OPENBAO__NATS__*`, `OPENBAO__SMB__{SMB_HOST, SMB_PATH, SMB_LOGIN, SMB_PASSWORD}` | `SCAN_HOTFOLDER_MODE=smb` (`NATS_ENABLED` по умолчанию `true`, задавать не нужно). `SCAN_HOTFOLDER_PATH` в контуре не нужен (только режим `local`; режим `local` и эмулятор `make emulator` — для разработки) |
| tabular-parser | `OPENBAO__MINIO__*`, `OPENBAO__NATS__*` | сверх секретов ничего (`NATS_ENABLED` по умолчанию `true`); при необходимости `PARSE_TIMEOUT_SECONDS` (и `PARSE_MAX_BYTES`) |
В отличие от arm-core, в `scan-gateway` и `tabular-parser` `NATS_ENABLED=true` по умолчанию, а `DEBUG` влияет только на
уровень логов; платформенной аутентификации у них нет (внутренние сервисы без HTTP-входа для пользователей).
Зависимости под контур: `PyJWT[crypto]==2.8.0` и `cryptography==43.0.3` (arm-core PR #50, issue #43; в том же PR
исправлена проверка JWKS на PyJWT 2.8.0).
Миграции и seed при деплое: из `api/` — `alembic upgrade head`, затем `python -m scripts.seed` (справочники: план счетов,
правила элиминации, роли и права; `--dev` только для стенда разработки). Те же команды — цели `make migrate` и `make seed`
(`README.md` arm-core, «Быстрый старт», шаг 4 «Миграции и справочники»).
Subjects NATS, схема ключей S3 и ресурсы — `context/03-architecture.md`.
### 6.3. Аутентификация в контуре (временно)
Контур временно **без платформенной аутентификации**: приложение доступно любому, кто его открыл. Вход идёт через
dev-заглушку: `POST /api/v1/dev/token {email}` выдаёт JWT, на фронте её использует форма DevLogin. Что для этого включено:
- `OPENBAO__APP__DEBUG=true` дополнительно включает CORS `*` и открытые `/docs`, `/redoc`, `/openapi.json`;
middleware `raisa_fastapi_protected_api` не импортируется (в arm-core он и так не подключён);
- `OPENBAO__APP__AUTH_DEV_ISSUER=true` подключает роутер dev-токенов (нужны оба флага: `DEBUG` и `AUTH_DEV_ISSUER`);
токены подписываются `AUTH_DEV_SECRET`.
Последствия, которые нужно понимать:
- пользователи с ролями должны быть в `app_user` **до входа** (seed); войти может любой, кто знает email пользователя,
то есть защита контура держится на сетевой изоляции, а не на аутентификации;
- `AUTH_DEV_SECRET` — секрет: случайная строка от 32 байт, хранится в OpenBao, в код и в `.env.example` не попадает.
Переход на SSO РАИСА (отключить `DEBUG`/`AUTH_DEV_ISSUER`, задать `AUTH_JWKS_URL` или `AUTH_PUBLIC_KEY` + `AUTH_ISSUER`,
`AUTH_AUDIENCE`, `AUTH_EMAIL_CLAIM`) — после ответа на OQ-54 (`context/07-open-questions.md`) и задачи arm-core#43.
## 7. Правки внутри контура и обратный поток
Правило: источник истины — репозитории снаружи. Любая правка в контуре (чаще всего под пайплайн платформы: пути, переменные,
`raisa_config.yml`) возвращается наружу до следующей синхронизации:
```bash
cd contour-arm-core
git diff <коммит sync> HEAD -- . ':!SNAPSHOT.md' > /tmp/contour-arm-core.patch # база — коммит последнего sync
```
Патч применяется снаружи в ветку и проходит обычный PR; после его мержа следующий снимок уже содержит правку, и
`rsync --delete` ничего не потеряет. Если правка касается только контура (например, секретов платформы), её место — вне
репозитория: значения в OpenBao (`OPENBAO__*`), а не код.
## 8. Чек-лист одной синхронизации
1. Снаружи: нужные PR слиты в `main` трёх сервисов; `make check-full` зелёный в каждом; контракт в `docs/contracts` и копии
`api/contracts` идентичны.
2. Правки из контура (раздел 7) возвращены наружу и слиты.
3. `prepare-contour-snapshot.sh` по каждому сервису; скрипт завершился словами «снимок готов».
4. В контуре: клон, ветка, `rsync --delete`, просмотр `git status`, коммит `sync: <repo>@<hash> (<дата>)`, `make venv`,
`make check` (пропуски — только из раздела 5), для arm-core `make web-check`, merge request по регламенту GitLab контура.
5. Порядок мержа при смене версии контракта: arm-core → tabular-parser → scan-gateway.
6. Снаружи в `context/09-delivery-plan.md` или в issue синхронизации отметить, какие хеши перенесены.
## 9. Известные неточности снимка
- `README.md` сервисов описывают `make venv` через `uv` и ссылаются на `../docs`, `../fixtures`, `docs/dev/qa-guide.md`;
скрипт их не переписывает. Ориентир для контура — этот документ и `SNAPSHOT.md`.
- В `openspec/changes/archive/*/` остаются относительные пути вида `../docs/openspec/...` и `../fixtures/...`: это
ссылки на внешний workspace, в контуре они никуда не ведут.
- Тесты, которые снаружи пропускаются без `fixtures`, в контуре не выполняются никогда. Поэтому перед каждым снимком снаружи
обязателен `make check-full` с доступными `docs` и `fixtures` (`dev/checks.md`, раздел «Порядок перед PR»).