arm-ka-docs/dev/checks.md
2026-10-27 17:06:45 +04:00

220 lines
24 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.

# Проверки качества: цели `make` и порядок перед PR
Конфигураций CI в репозиториях нет (в Gitea CI не используется, пайплайн платформы банка вне зоны проекта), поэтому
проверки выполняются локально одной командой перед каждым PR. Требования — spec `quality-checks`
(`openspec/specs/quality-checks/spec.md`), решение D-8 (`openspec/changes/archive/2026-10-04-slice-0-skeleton/design.md`).
Все цели запускаются **из корня репозитория сервиса** (`arm-core`, `tabular-parser`, `scan-gateway`); рецепты сами
переходят в `api/` и используют окружение `api/.venv`. Инфраструктура compose — `docs/dev/docker-compose.yml`
(см. [`README.md`](README.md)); в workspace и в worktree `.wt/<срез>/` она лежит рядом как `../docs/dev/`.
## Общие цели
Одинаковый набор целей во всех трёх сервисных репозиториях; семантика, описанная ниже, — эталонная (реализации
расходятся в деталях, см. «Известные расхождения реализаций»). Репозиторий вправе добавлять свои цели (см. ниже), но
не меняет смысл общих.
| Цель | Что делает | Нужен Docker / compose |
|---|---|---|
| `make venv` | `api/.venv` на Python 3.11 через `uv`; ставит `requirements.txt` без строк `raisa_*` и `pylib_ai*` (пакеты Nexus банка вне банка недоступны, D-10), `requirements-dev.txt` и библиотеку `arm_statements` из `STATEMENTS_DIR` | нет |
| `make lint` | `ruff check .`, `ruff format --check .`, `mypy src` (в `arm-core` — `mypy src scripts`) | нет |
| `make test` | `pytest -m "not integration"`: юнит-тесты; **включает тест идентичности вендоренного контракта** `api/contracts/` с `../docs/contracts` | нет |
| `make test-integration` | `pytest -m integration` против инфраструктуры compose (PostgreSQL 16, NATS, MinIO; бакет `arm-ka` должен существовать, его создаёт `make up`) | да, `make up` |
| `make check` | `make lint` затем `make test`; останавливается на первой упавшей проверке (только при последовательном запуске: `lint` и `test` — пререквизиты, при `make -j` они идут параллельно) | нет |
| `make check-full` | `make check` + `make test-integration` + сборка docker-образа (`docker build`, с библиотекой `arm_statements` из `STATEMENTS_DIR`, см. ниже); для `arm-core` — расширенный набор, см. ниже | да, `make up` и Docker |
| `make up` | поднимает инфраструктуру из `../docs/dev/docker-compose.yml`; постоянные сервисы `postgres`, `nats`, `minio` | Docker |
Сводка шагов `make check`: `ruff check .` → `ruff format --check .` → `mypy src` (в `arm-core` — `mypy src scripts`) →
`pytest -m "not integration"`.
Любой ненулевой код останавливает цель на этом шаге, следующие не выполняются. Запускайте без `-j`: при параллельной сборке шаги идут одновременно. `make check` не требует Docker,
PostgreSQL, NATS и сети.
### `make up`
Поднимает compose-проект `arm-ka` (имя зафиксировано в compose, поэтому тома общие для любого worktree):
```bash
docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka up -d --wait postgres nats minio
docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka run --rm minio-init # бакет arm-ka (идемпотентно)
```
Почему так: голый `docker compose up -d --wait` возвращает код 1 (одноразовый `minio-init` завершается кодом 0, compose
считает это сбоем), поэтому `--wait` даётся со списком долгоживущих сервисов, а бакет создаётся отдельным шагом. Без
бакета интеграционные тесты на свежем томе падают на `NoSuchBucket`. Целей `down`/`stop`/`restart` нет намеренно: проект
`arm-ka` общий для всех сервисов; остановка и сброс — вручную (`docker compose --profile services down`, при сбросе данных `down -v` с тем же `--profile services`,
иначе контейнеры сервисов останутся), см. `README.md`.
> Эталонная реализация `make up` — в `scan-gateway`. Реализации в `arm-core` и `tabular-parser` отличаются
> (см. «Известные расхождения реализаций»).
### Состав `make check-full` по репозиториям
| Репозиторий | Шаги по порядку |
|---|---|
| `scan-gateway` | `check` → `test-integration` → `docker-build` (образ `scan-gateway:dev`); интеграционный тест поднимает эмулятор ИС Сканирование в процессе теста |
| `tabular-parser` | `check` → `test-integration` → `docker-build` (образ `tabular-parser`) |
| `arm-core` | `check` → `dbml-check` → `check-infra` → `test-integration` → `docker-build` → `web-check` |
### Библиотека общего кода `arm_statements`
Общий код нормализации ББ/ОФР (`05-domain-rules.md` §11) — Python-библиотека в репозитории `RSHB/pylib_ai_arm_statements`:
дистрибутив `pylib-ai-arm-statements`, импорт `arm_statements` (AURORA-1677; до 27.10.2026 — копии `api/arm_statements/`
в сервисах и скрипт `sync-statements`, история — архив change `unified-statement-normalization`). Раздел описывает поведение
по спеке `statement-normalization` (требование «Поставка библиотеки потребителям»).
- **Версия:** семантическая, файл `VERSION` в корне библиотеки и тег `v<версия>` на коммите выпуска; опубликованная версия
не пересобирается с другим содержимым. В Nexus библиотеку публикует платформа.
- **Подключение:** в `api/requirements.txt` каждого сервиса — `pylib_ai_arm_statements==<версия>`; на платформе ставится из
Nexus. Вне банка `make venv` пропускает строки `pylib_ai*` (как `raisa_*`) и ставит библиотеку из checkout
`STATEMENTS_DIR` (по умолчанию `../pylib_ai_arm_statements`); после правки библиотеки — `make install`. Обе цели
сначала выполняют `statements-version`: `VERSION` checkout ≠ версии в `requirements.txt` — ошибка, checkout не на теге
`v<версия>` — предупреждение. `STATEMENTS_TAG` по умолчанию берётся из `requirements.txt`.
- **Сборка образа:** `make docker-build` передаёт checkout контекстом `statements` и отказывает, если `HEAD` checkout не
на теге `STATEMENTS_TAG`. compose (`dev/docker-compose.yml`) собирает все три сервиса с `STATEMENTS_DIR` из
`dev/.env` (по умолчанию `../../pylib_ai_arm_statements` от `docs/dev`).
- **Линтеры и тесты:** библиотека проверяется своим `make check` (ruff, mypy, pytest) в своём репозитории; `make check`
сервиса её исходники не обходит, `arm_statements` для `ruff` сервиса — сторонний пакет.
- **Маркер версии:** `service.statements_version` результата — версия установленного дистрибутива (`importlib.metadata`);
ядро сравнивает её с `statements_version` задания при пересчёте после правки (`STATEMENTS_VERSION_MISMATCH`, `info`).
У заданий до перехода в версии хэш коммита копии — расхождение ожидаемо (решение владельца 26.10.2026).
- **Перечень кодов форм:** источник — `contracts/statement-code-orders.yaml`, в библиотеке — байтовая копия
`arm_statements/rules/statement_code_orders.yaml`. Порядок правки: PR в docs → PR в библиотеку и новая версия → подъём
версии в сервисах («сначала ядро»). Сверку docs с библиотекой делает `make -C docs statements-orders-check` (входит в
`make -C docs check`, расхождение — предупреждение); что все коды словаря имён ядра есть в перечне — тест ядра
`test_name_dictionary_codes_in_code_orders`.
Для `arm-core` дополнительно:
- `dbml-check` — `python scripts/check_dbml_vs_orm.py ../../docs/model/data-model-v5.19.dbml` (путь от `api/`, по умолчанию — переменная `DBML` Makefile, она указывает на текущую версию модели;
другой — `make dbml-check DBML=<путь относительно api/>`): ORM обязана совпадать с DBML; расхождения печатаются
построчно, код 1.
- Цикл миграций «`alembic upgrade head` → `alembic downgrade base` → `alembic upgrade head`» на чистой БД, пустой
autogenerate и сверка схемы с `create_all` ORM выполняет `tests/test_migrations.py`; он входит в `test-integration`,
поэтому отдельным шагом в `check-full` не гоняется. Для быстрой проверки одних миграций: `make migration-cycle`.
- `check-infra` — TCP-проверка доступности PostgreSQL и NATS из `api/.env.test`; вместо длинного трейсбека pytest
подсказывает «поднимите инфраструктуру: `make up`».
- `web-check` — `npm install`, `biome check src`, `tsc --noEmit`, `npm run build` в `web/`. Юнит-тесты и линтер фронта
в `web-check` не входят — их запускают из `web/`: `npm test` (vitest) и `npm run lint` (ESLint + Prettier).
Дополнительные цели репозиториев (не общие): `arm-core` — `migrate`, `seed`, `seed-dev`, `dbml-check`,
`migration-cycle`, `check-infra`, `docker-build`, `web-check`; `scan-gateway` — `help`, `docker-build`, `emulator`;
`tabular-parser` — `docker-build`.
## Эталонный фрагмент `dev/Makefile.common`
Рецепты общих целей вынесены в [`Makefile.common`](Makefile.common) — это эталон (источник — `Makefile` `scan-gateway`),
с которым сервис сверяется `diff`ом или который подключает напрямую:
```make
# Makefile сервиса: свои переменные и цели; цель по умолчанию задана явно, иначе ею станет первая цель include (venv)
.DEFAULT_GOAL := check
MYPY_PATHS := src scripts # arm-core; по умолчанию src
CHECK_FULL_STEPS := check dbml-check check-infra test-integration docker-build web-check # arm-core
include ../docs/dev/Makefile.common
```
Переменные (все `?=`): `VENV`, `BIN`, `MYPY_PATHS`, `COMPOSE_FILE`, `COMPOSE_PROJECT`, `IMAGE` (по умолчанию имя каталога
сервиса; у `scan-gateway` — `scan-gateway:dev`), `CHECK_FULL_STEPS` (по умолчанию `check test-integration docker-build`).
Определяет цели `venv`, `lint`, `test`, `test-integration`, `check`, `docker-build`, `check-full`, `up`.
Цели, специфичные для сервиса (`migrate`, `seed`, `dbml-check`, `check-infra`, `web-check`, `emulator`, `help`),
остаются в его `Makefile`.
Проверка «`make check` вызывает одинаковые команды»: `make -n <цель>` в каждом сервисе и в Makefile, подключающем
`Makefile.common` с теми же переменными, даёт одинаковый вывод. Текущее состояние (Срез 0, сервисы не менялись):
`scan-gateway` совпадает с эталоном полностью; `arm-core` и `tabular-parser` — по `venv`, `lint`, `test`,
`test-integration`, `check`, `docker-build` и составу команд `check-full`, но не по `up` и по структуре `check-full`
(пререквизиты вместо вызовов `$(MAKE)`); см. «Известные расхождения реализаций». Сервисы переходят на `include` в
следующих срезах (решение 24 в `design.md`).
## Внешние ресурсы: docs и fixtures опциональны
Проверки сервисов обращаются к соседним репозиториям workspace и **не должны падать**, если их нет: тест
пропускается (`skipped`) с явной причиной, остальные выполняются, `make check` остаётся зелёным.
| Что | Где ищется | Что пропускается без него |
|---|---|---|
| `docs` | `../docs` или каталог из `ARM_KA_DOCS` | тест идентичности контракта `api/contracts/` с `docs/contracts/`; в `arm-core` ещё сверка с DBML в тестах (`dbml-check` как цель `make` требует DBML и падает без него) |
| `fixtures` | `../fixtures` или каталог из `ARM_KA_FIXTURES` (в `arm-core`; в остальных сервисах — см. «Известные расхождения реализаций») | тесты с маркером `fixtures` (реальные данные, внутренний доступ): причина пропуска — «ARM_KA_FIXTURES недоступен …» (в `arm-core`) |
Пример (docs и fixtures лежат не рядом):
```bash
export ARM_KA_DOCS=/path/to/arm-ka-docs
export ARM_KA_FIXTURES=/path/to/arm-ka-fixtures
make check
```
Причины пропусков видно в выводе (`pytest --verbose` в `addopts`: строки `SKIPPED (…)`); полный список —
`cd api && .venv/bin/pytest -m "not integration" -rs`.
Важно: зелёный `make check` с пропусками **не** доказывает идентичность контракта и прохождение на реальных данных. Перед
PR убедитесь, что `docs` и `fixtures` доступны (пропусков по этим причинам нет) либо явно укажите в описании PR, что
пропущено.
> Фактическая поддержка переменных различается по репозиториям — см. «Известные расхождения реализаций».
## Проверки этого репозитория (`docs`)
В репозитории `arm-ka-docs` нет сервисных целей; `Makefile` в его корне содержит только проверки спеки и контракта:
| Цель | Что делает |
|---|---|
| `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) |
| `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml` (коды — 4 цифры формы; в варианте `full` код стоит 1 + (вхождений в `repeats`) раз, в `simplified` — не чаще); код 1 при любой ошибке |
| `make validate-openspec` | `openspec validate --all --strict` — все спеки и незаархивированные change'и (один change: `make validate-openspec CHANGE=<id>`) |
| `make check` | все проверки выше, `test-snapshot` и `statements-orders-check` |
| `make statements-orders-check` | `contracts/statement-code-orders.yaml` против байтовой копии в библиотеке (`STATEMENTS_DIR`, по умолчанию `../pylib_ai_arm_statements`); расхождение или нет checkout — предупреждение, код 0 |
Без `make`: `python scripts/validate_contracts.py` (нужны `jsonschema` и `PyYAML`) и `openspec validate <id> --strict`.
Менять `contracts/**` нужно осознанно: байтовые копии схем лежат в `api/contracts/` каждого сервиса, после правки их
нужно пересинхронизировать (`cp`), иначе тест идентичности в сервисах упадёт. Правило теста идентичности (у ядра и
сервисов одно, верно до, во время и после окна перехода между MAJOR — сейчас 1 → 2): схема копии с `$id` `…/v<M>`
сверяется с `<имя>.v<M>.schema.json`, **если такой файл есть в docs**, иначе с основным (у `recognition-request` файла
`.v1` нет — копия v1.0 сверяется с основным); пример `examples/<имя>.json` копии — с `examples/v<M>/<имя>.json`, если он
есть, иначе с `examples/<имя>.json` (`M` — MAJOR копии схемы этого вида, вид — по префиксу имени); прочие файлы копии
(`*.v<N>.schema.json`, `examples/v<N>/` у ядра) — с одноимёнными; файлы docs другого MAJOR сервис не сверяет. Правило
вводится во все три репозитория до слияния контракта v2.0 в docs (шаг 0 выкладки; спека `recognition-contract`,
требование «Копия контракта в сервисе идентична источнику»). Ядро держит копии обоих MAJOR. Правило реализовано файлом `api/tests/contract_identity.py`, побайтно одинаковым в трёх репозиториях; правка — сразу во всех трёх. Перечень кодов `contracts/statement-code-orders.yaml`
копируется не в сервисы, а в библиотеку `arm_statements` (сверяет `make statements-orders-check`).
## Порядок перед PR
1. Поднять инфраструктуру: `make up` (в репозитории сервиса). Убедиться, что `docs` и `fixtures` доступны (рядом или через
`ARM_KA_DOCS`, `ARM_KA_FIXTURES` — последняя поддерживается только в `arm-core`, см. «Известные расхождения
реализаций»).
2. `make check` — быстрые проверки. Исправлять до зелёного.
3. `make check-full` — полная проверка (интеграционные тесты, образ; для `arm-core` — ещё DBML, миграции, `web`).
4. Если менялись `contracts/`, `model/` или спека в репозитории `docs`: в нём `make check`, затем в каждом затронутом
сервисе — пересинхронизация копий контракта и пункты 2–3.
5. Итог `make check-full` (зелёный/красный, число тестов, **что пропущено и почему**) вписать в описание PR.
Правила: PR создаётся только после зелёного `make check-full`; один OpenSpec change и один PR — на один репозиторий;
`make check-full` не заменяет ревью.
## Типичные проблемы
- **`make test-integration` падает на соединении** — не поднята инфраструктура: `make up`; порты заняты — поменять в
`docs/dev/.env` и в `api/.env.test`/`api/.env` сервиса (`README.md`).
- **`NoSuchBucket`** — бакета нет на свежем томе: `docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka
run --rm minio-init`.
- **`tabular-parser`: интеграционный тест получает чужие ответы** — на шине compose не должно работать другого
экземпляра сервиса из той же queue group (интеграционный тест поднимает сервис в процессе теста).
- **Нет `uv`/Python 3.11** — `make venv` их требует (версия Python — как на платформе).
- **`docker build` (и `npm install` в `web-check` у `arm-core`) падает без сети** — это шаги `check-full`, которым нужна сеть (образы, пакеты); `make check` сети не требует.
## Известные расхождения реализаций
Фактические реализации в `Makefile` трёх сервисов расходятся с описанной выше семантикой. Сервисы в рамках Среза 0
не менялись; расхождения занесены как находки ревью.
- **`make up`.** Эталон — `scan-gateway` (`--project-name arm-ka`, `--wait` со списком сервисов, затем `run --rm
minio-init`). `tabular-parser` не создаёт бакет (нет шага `minio-init`) и на свежем томе даёт `NoSuchBucket`;
`arm-core` выполняет `docker compose up -d` без `--wait`, то есть возвращается до готовности сервисов.
- **Структура `check-full`.** В `scan-gateway` шаги вызываются через `$(MAKE)` (порядок и остановка на первой ошибке
гарантированы и при `make -j`); в `arm-core` и `tabular-parser` — пререквизитами (при последовательном запуске
эквивалентно, при `make -j` порядок не гарантирован). Запускайте без `-j`.
- **`ARM_KA_DOCS`/`ARM_KA_FIXTURES`.** `arm-core` учитывает обе переменные; `tabular-parser` — только `ARM_KA_DOCS`
(тестов с маркером `fixtures` пока нет); `scan-gateway` учитывает `ARM_KA_DOCS` в тесте идентичности контракта (с шага 0
#116), а `ARM_KA_FIXTURES` — нет (ищет `../fixtures/ocr` по фиксированному пути; тексты причин пропуска не содержат
«ARM_KA_FIXTURES недоступен»). Пока расхождение по fixtures не устранено, для `scan-gateway` fixtures нужно класть рядом.