# Проверки качества: цели `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_*` (пакеты Nexus банка вне банка недоступны, D-10) и `requirements-dev.txt` | нет | | `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`) + `statements-check` (свежесть копии общего кода `arm_statements`, см. ниже); для `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` → `statements-check` → `test-integration` → `docker-build` (образ `scan-gateway:dev`); интеграционный тест поднимает эмулятор ИС Сканирование в процессе теста | | `tabular-parser` | `check` → `statements-check` → `test-integration` → `docker-build` (образ `tabular-parser`) | | `arm-core` | `check` → `statements-check` → `dbml-check` → `check-infra` → `test-integration` → `docker-build` → `web-check` | `statements-check` появляется в сервисе вместе с копией пакета (PR подключения хоста); до этого цели нет. ### Копия общего кода `arm_statements` и `sync-statements` Общий код нормализации ББ/ОФР (`05-domain-rules.md` §11) живёт в репозитории `arm-statements` пакетом `arm_statements/`; в `arm-core`, `tabular-parser` и `scan-gateway` он лежит **копией** — прямые правки копии запрещены (Р-1, design `openspec/changes/archive/2026-10-19-unified-statement-normalization/design.md`). Скрипт и цели docs (`make -C docs sync-statements`, `statements-check`, `test-sync-statements`) — arm-ka-docs#120; `statements-check` сервисов появляется с PR подключения копии в каждом хосте (план 2, задачи 7–9) — до шага 3 выкладки. Раздел описывает поведение по спеке `statement-normalization` (требование «`sync-statements`…»). - **Где копия:** `api/arm_statements/` (рядом с `api/src/`; импорт `import arm_statements` — `api/` в `sys.path`, как для `src`). Пакет приносит свои тесты (`api/arm_statements/tests/`); конфиг линтеров пакета в копию не кладётся — хост держит `arm_statements` в строгом блоке `mypy` и в `testpaths` (P2-1, P2-3); `ruff`, `pytest` и `mypy` хоста охватывают копию: `ruff` и `pytest` — по каталогу `api/` (тесты пакета подключены как `testpaths`), `mypy` — `MYPY_PATHS` хоста дополняется `arm_statements` (`mypy src arm_statements`, в `arm-core` — `mypy src scripts arm_statements`). - **Маркер версии:** `api/arm_statements/VERSION` — полный хэш коммита `arm-statements`, из которого разложена копия; пишет только скрипт. Сервис при старте читает его и пишет в результат `service.statements_version`; ядро сравнивает его с `statements_version` задания при пересчёте после правки (`STATEMENTS_VERSION_MISMATCH`, `info`). - **Скрипт:** `docs/scripts/sync-statements.sh`, запуск из workspace или `make -C docs sync-statements SYNC_ARGS=…`. Источник — локальный checkout `arm-statements` рядом (`../arm-statements`) или `--source `, ссылка — `origin/main` или `--ref `; хост — `--host arm-core|tabular-parser|scan-gateway` или `--host-dir `. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет `VERSION`, заводит issue и PR в хосте; `--dry-run` — то же без issue, проверок, коммита и PR. С `--in-place` — раскладывает копию в рабочее дерево существующего worktree хоста без git-операций (подключение хоста; PR фичи, которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — отказ `20`, ничего не пишется). Обзор по трём хостам рядом — `make -C docs statements-check`; сборка снимка для контура (`prepare-contour-snapshot.sh`) вызывает `--check` для хостов, где копия уже есть (Р-42). - **`--check`, коды возврата** (файл `VERSION` в сравнении не участвует — в источнике его нет): `0` — копия равна дереву `arm_statements/` коммита из `VERSION`, это коммит `--ref`, и копия перечня кодов равна docs; `10` — копия равна дереву коммита из `VERSION`, но в `--ref` есть более новый (копия отстала), или байтовая копия перечня кодов `arm_statements/rules/statement_code_orders.yaml` отличается от `docs/contracts/statement-code-orders.yaml` (перечень в docs правился раньше `arm-statements` или пересинхронизации хоста) — предупреждение, не ошибка; `20` — копия отличается от дерева коммита из `VERSION` (прямая правка), `VERSION` нет в истории источника, зависимости пакета (список `dependencies` его `pyproject`) отсутствуют в зависимостях хоста, либо (только `arm-core`) код словаря имён `api/rules/statement_lines.yaml` отсутствует в перечне своей редакции копии пакета; `2` — нет checkout `arm-statements` или хоста, неизвестный аргумент. Файлы расхождения перечисляются в выводе. Порядок правки перечня кодов: PR в docs → PR в `arm-statements` → `sync-statements` в хостах («сначала ядро»); до конца цепочки хосты получают `10`. - **Конфиг линтеров и тесты пакета:** пакет проходит самый строгий конфиг `ruff` / `mypy` среди хостов (в своём репозитории), потому что конфиг хоста применяется к копии, а править копию нельзя; тесты пакета — со своим `conftest.py`, `autouse`-фикстуры `conftest` хоста ограничены каталогом `tests/` хоста. - **`statements-check` в `make check-full`:** `../docs/scripts/sync-statements.sh --check --host-dir .` (docs — `../docs` или `ARM_KA_DOCS`); код `0` и `10` — цель зелёная (при `10` предупреждение печатается, в описание PR вписывается, что копия отстала), `20` и `2` — цель красная. В `make check` и в пайплайн платформы не входит: тест, читающий соседний репозиторий, уже ломал `main` (arm-core #33). Для `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=`) | | `make check` | все проверки выше и `test-snapshot`, `test-sync-statements` | | `make test-sync-statements` | сценарии `sync-statements.sh` на синтетических репозиториях: коды `--check`, `--in-place`, `--dry-run`, отказ при прямой правке | | `make sync-statements` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run]"` | | `make statements-check` | обзор свежести копий по трём хостам рядом (`arm-core`, `tabular-parser`, `scan-gateway`): строка итога на хост; в `check` не входит | Без `make`: `python scripts/validate_contracts.py` (нужны `jsonschema` и `PyYAML`) и `openspec validate --strict`. Менять `contracts/**` нужно осознанно: байтовые копии схем лежат в `api/contracts/` каждого сервиса, после правки их нужно пересинхронизировать (`cp`), иначе тест идентичности в сервисах упадёт. Правило теста идентичности (у ядра и сервисов одно, верно до, во время и после окна перехода между MAJOR — сейчас 1 → 2): схема копии с `$id` `…/v` сверяется с `<имя>.v.schema.json`, **если такой файл есть в docs**, иначе с основным (у `recognition-request` файла `.v1` нет — копия v1.0 сверяется с основным); пример `examples/<имя>.json` копии — с `examples/v/<имя>.json`, если он есть, иначе с `examples/<имя>.json` (`M` — MAJOR копии схемы этого вида, вид — по префиксу имени); прочие файлы копии (`*.v.schema.json`, `examples/v/` у ядра) — с одноимёнными; файлы docs другого MAJOR сервис не сверяет. Правило вводится во все три репозитория до слияния контракта v2.0 в docs (шаг 0 выкладки; спека `recognition-contract`, требование «Копия контракта в сервисе идентична источнику»). Ядро держит копии обоих MAJOR. Правило реализовано файлом `api/tests/contract_identity.py`, побайтно одинаковым в трёх репозиториях; правка — сразу во всех трёх. Перечень кодов `contracts/statement-code-orders.yaml` копируется не в сервисы, а в пакет `arm_statements` (сверяет `sync-statements --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 нужно класть рядом.