arm-ka-docs/dev/checks.md
Andrey Epifancev 9bc2b26c1c scripts/regression: полная регрессия main всех репозиториев перед выкладкой (#179)
regress.sh — один вход: preflight (main = origin/main, чистые, нет чужих потребителей NATS), venv, lint+unit
всех репозиториев и docs make check, фронт в чистой копии (npm ci --dry-run как проверка lock, установка без
lock), интеграционные с циклом миграций, реальные данные парсера, образы, e2e ядра, изолированный стенд с нуля
(compose-проект arm-ka-regress на своих портах) со сценарием по API, UI-смоук Playwright, парсер по doc-bank.
Не останавливается на первой ошибке; итог — summary.md с SHA и статусом шагов.

Контрольные цифры — expected.json (qa-guide §7.1–7.3, прогоны 08.10/14.10, golden шахматки); снимки свода,
шахматки и форм сравниваются с прошлым прогоном. Ссылки из dev/checks.md и чек-листа синхронизации контура.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:09:15 +04:00

248 lines
28 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_*` (пакеты 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 <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или
`--host-dir <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`.
### Перед выкладкой: полная регрессия
`make regress` в docs (`scripts/regression/regress.sh`) прогоняет всё перечисленное выше по `main` всех репозиториев
сразу, не останавливаясь на первой ошибке, плюс то, чего нет в `check-full`: фронт в чистой копии с `npm ci`, e2e ядра с
настоящим парсером, стенд с нуля в отдельном compose-проекте со сценарием по API и контрольными цифрами, UI-смоук и
прогон парсера по банку первички со сравнением с прошлым прогоном. Описание этапов и итога — `scripts/regression/README.md`.
## Эталонный фрагмент `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`, `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 <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` (сверяет `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 нужно класть рядом.