Andrey Epifancev f12ce87346 docs: соглашения шаблона в CLAUDE.md и OpenSpec change slice-0-skeleton
Срез 0 (issue #1): раздел «Соглашения шаблона» (что оставлено от шаблона команды, что
вырезается, что добавляется); change openspec/changes/slice-0-skeleton — proposal, design
с таблицей «Decisions made without discussion», спеки и tasks; в openspec/config.yaml
строки с двоеточием взяты в кавычки (YAML разбирал их как словари).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 02:43:34 +04:00

108 lines
12 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.

# Tasks: Срез 0 — скелет ядра arm-core
Репозиторий — arm-core, если не указано иное; все команды — из `api/`. Порядок — по зависимостям. Каждая задача ≤ половины
дня, с указанием файлов и способа проверки. Зависимости от других репозиториев: 1.1 ждёт контракт v1.0
(`docs` задачи 1.1–1.4); 2.x нужна инфраструктура compose (`docs` 2.1).
## 1. Инструменты и окружение шаблона
- [ ] 1.1 `api/pyproject.toml` (ruff: line-length 120, правила E,F,I,UP,B,PL; mypy с плагином pydantic),
`requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest, pytest-asyncio, pytest-env, pytest-cov, httpx,
PyJWT[crypto], pydbml, PyYAML, types-PyYAML; проверка: `pip install -r requirements-dev.txt` без конфликтов,
`ruff check . && ruff format --check . && mypy src` зелёные на текущем каркасе
- [ ] 1.2 `src/core/config.py`: `Settings.model_config` читает `env_file` из `ENV_FILE` (по умолчанию `.env`); новые поля
`AUTH_DEV_ISSUER=false`, `AUTH_DEV_SECRET`, `AUTH_JWKS_URL`, `AUTH_PUBLIC_KEY`, `AUTH_ISSUER`, `AUTH_AUDIENCE`,
`AUTH_EMAIL_CLAIM="email"`, `NATS_ENABLED=false`, `NATS_MANAGE_STREAM=false`, `OUTBOX_POLL_SECONDS=5`;
`.env.example`, `.env.test`; проверка: `ENV_FILE=.env.test python -c "from src.core.config import settings"` читает тестовые значения
- [ ] 1.3 `Dockerfile` (python:3.12-slim, `pip install -r api/requirements.txt`, `CMD python run_server.py`), убрать строку
`Dockerfile` из `.gitignore`, `Makefile` (`up`, `migrate`, `seed`, `test`, `lint`, `dbml-check`); проверка:
`docker build -t arm-core .` успешен, `make lint` зелёный
- [ ] 1.4 `src/app.py`: NATS — пустые `NATS_SEED/NATS_JWT` → подключение без nkeys (перенести `nkeys.from_seed` из
модуля в функцию), подключение при `NATS_ENABLED=true` независимо от `DEBUG`; `api/router.py` — `APIRouter(prefix="/api/v1")`;
проверка: `python run_server.py` стартует против compose без учётных данных, `/healthcheck` 200
## 2. Модель данных (spec `data-model`)
- [ ] 2.1 `src/models/enums.py`: 27 enum'ов DBML как `enum.Enum` + фабрика `pg_enum`; `DBML_VERSION = "5.13"` в
`models/__init__.py`; проверка: тест перечисляет имена и значения против списка из DBML (парсинг `Enum {}` блоков через pydbml)
- [ ] 2.2 Части 01–03: `directories.py` (`regional_office`, `company_group(+_history)`, `legal_entity(+_history)`,
`legal_entity_alias`, `related_group`, `related_group_member` с partial unique), `access.py` (`app_user`, `role`,
`permission`, `role_permission`, `user_role`), `accounting.py` (`chart_of_account`, `elimination_rule`,
`elimination_rule_account`, `required_document_rule`); проверка: `Base.metadata.create_all` на тестовой БД без ошибок
- [ ] 2.3 Части 04–06: `files.py` (`s3_object`, `stored_file`, `document` с `NULLS NOT DISTINCT`, `recognition_job`;
FK-цикл через `use_alter`), `osv.py` (`osv_entry`), `statements.py` (`financial_statement_meta/column/line/value`);
проверка: как 2.2
- [ ] 2.4 Части 07–09: `consolidation.py` (`consolidation`, `_version`, `_status_history`, `_lock` с partial unique,
`_member`, `_document`), `kfo.py` (`correction`, `goodwill`, `kfo_result`, `kfo_result_line`, `kfo_result_line_company`,
`client_kfo_line`), `audit_ex.py` (`comment`, `audit_event`, `ex_snapshot`, `ex_snapshot_member`, `ex_snapshot_item`);
проверка: как 2.2, всего 43 таблицы в `Base.metadata.tables`
- [ ] 2.5 `scripts/check_dbml_vs_orm.py` (pydbml; сравнение таблиц, колонок, типов, nullability, PK/UNIQUE/FK, индексов с
notes-шаблонами, enum'ов, версии) + `tests/test_dbml_parity.py`; проверка: 0 расхождений на 2.1–2.4; намеренно
добавленная колонка даёт код 1 и понятное сообщение
- [ ] 2.6 `alembic/env.py`: `CREATE SCHEMA IF NOT EXISTS dkb` перед миграциями; ревизия `0001_dbml_v5_13.py` (черновик
autogenerate → ручная правка: enum'ы, partial-индексы, `NULLS NOT DISTINCT`, `use_alter`, полный `downgrade`);
проверка: `alembic upgrade head && alembic downgrade base && alembic upgrade head` на compose-PostgreSQL;
`alembic revision --autogenerate` после — пустая ревизия
- [ ] 2.7 `tests/conftest.py`: engine из `.env.test`, миграции один раз на сессию, транзакция с откатом на тест, маркеры
`integration`/`fixtures` (skip с причиной при отсутствии `ARM_KA_FIXTURES`); проверка: тест чтения `Decimal` из
`osv_entry.closing_debit` проходит; `pytest -m "not integration"` зелёный
## 3. Seed (spec `reference-data-seed`)
- [ ] 3.1 `seed/core/roles.yaml`, `permissions.yaml` (16 кодов), `role_permissions.yaml` (матрица §10, ASSUMPTION(OQ-16)),
`regional_office.yaml` (ГО); `scripts/seed.py` — загрузчик yaml/csv с upsert по натуральному ключу, флаг `--dev`;
проверка: двойной запуск — одинаковое число строк; тест сравнивает `role_permission` с таблицей §10
- [ ] 3.2 `seed/core/chart_of_accounts.csv`: План счетов 94н (01–99, 001–011, `kind`, `row_type`, `is_off_balance`) +
субсчета, упомянутые в §4/§6 правил и чек-листе (60.xx, 62.xx, 58.01/58.03, 66.xx/67.xx, 76.03/.05/.07.x/.08/.09,
90.01/90.03, 91.x, 80, 83, 03); проверка: все `parent_code` существуют, код уникален, нормализован (`60.01`, не `60.1`)
- [ ] 3.3 `seed/core/elimination_rules_v1.yaml` — 8 правил ББ из §4 со счетами сторон и исключениями;
`seed/core/required_document_rules.yaml` — BASIC (10) и EXTENDED (27, ASSUMPTION(OQ-46)); загрузчик падает до записи
при неизвестном счёте; проверка: сценарии «Правило № 6» и «Ссылка на несуществующий счёт» из spec
- [ ] 3.4 `seed/dev/regional_offices.yaml` (2 РФ), `seed/dev/users.yaml` (7 пользователей `<role>@armka.local` + `user_role`);
`make seed` / `make seed-dev`; проверка: без `--dev` `app_user` пуст; с `--dev` — 7 пользователей по одной роли
## 4. Аутентификация и права (spec `authn-authz`)
- [ ] 4.1 `src/core/auth.py`: `Claims`, протокол `TokenVerifier`, `DevHs256Verifier`, `JwksVerifier` (PyJWT, RS256/ES256,
кэш JWKS), фабрика по настройкам; проверка: unit-тесты — валидный/истёкший/чужой ключ, dev-verifier не создаётся при
`AUTH_DEV_ISSUER=false`
- [ ] 4.2 `src/repositories/users.py` (пользователь по email, эффективные права одним запросом), `api/dependencies/auth.py`
(`current_user` → 401/403, `require(code)`), `src/schemas/me.py`; проверка: тесты на dev-пользователях seed —
сценарии «Право есть», «Права нет», «Две роли»
- [ ] 4.3 `api/endpoints/me.py` (`GET /api/v1/me`), `api/endpoints/dev_auth.py` (`POST /api/v1/dev/token`, подключается
только при `AUTH_DEV_ISSUER and DEBUG`); проверка: тесты — ответ `/me` для `ka_rf@armka.local` равен правам §10;
при `DEBUG=false` маршрут `dev/token` → 404 и dev-токен → 401
- [ ] 4.4 `README.md` репозитория: замена шаблонного текста — назначение, команды (`make`), env, получение dev-токена,
запуск против compose; проверка: новый разработчик проходит README до зелёного `/me` (самопроверка по шагам)
## 5. Обмен через `recognition_job` и аудит (spec `recognition-job-exchange`, `audit-trail`)
- [ ] 5.1 `api/contracts/` — копии трёх схем из `../docs/contracts/` + `examples/`; `src/contract/` — pydantic-модели
запроса, конверта результата, файла результата (Decimal как строка); `tests/test_contract.py` — идентичность с
`../docs/contracts` (skip с предупреждением без docs), валидация примеров; проверка: тесты зелёные
- [ ] 5.2 `src/services/audit.py` — `AuditService.record(...)` в текущей сессии; тест: системное событие с `user_id = NULL`,
откат транзакции не оставляет записи; статический тест — в `src/` нет `update()/delete()` по `AuditEvent`
- [ ] 5.3 `src/core/nats.py`: подключение (с nkeys/JWT или без), `ensure_stream` (`RECOGNITION`, `recognition.>`, дубликаты
2 мин), durable pull-consumer `arm-core-results` (`max_deliver` 5, `ack_wait` 60 с); проверка: интеграционный тест
против compose-NATS создаёт stream и consumer, повторный вызов идемпотентен
- [ ] 5.4 `src/services/recognition_exchange.py`: `publish_pending()` (SKIP LOCKED, сборка запроса, `Nats-Msg-Id`,
переход `published`), `expire_deadlines()`; фоновые задачи в lifespan при `NATS_ENABLED`; проверка: интеграционный
тест — вставка `s3_object` + `stored_file` + `recognition_job(queued)` → сообщение получено тестовым подписчиком и
проходит схему → строка `published`; задание с прошедшим `reply_deadline` → `timeout`
- [ ] 5.5 `handle_result()` + `ResultMapper`/`NoopMapper` + регистрация `s3_object` результата + аудит
`CONSOLIDATION_STATUS_CHANGE`? — нет: для заданий аудит не предусмотрен enum'ом, пишем только лог; обработка
дубликатов, неизвестного `job_id`, `dlq`; проверка: интеграционный тест — `done` обновляет задание и создаёт
`s3_object`; повтор не меняет строки; `error NOT_IMPLEMENTED` → `error_code`; пятикратное падение → `dlq`
## 6. CI и завершение
- [ ] 6.1 `.gitea/workflows/ci.yml` из `../docs/dev/ci/python-service.yml` (блок `env`: arm-core, шаги `migrations`,
`dbml-check` включены) + job `web` (`npm ci`, `biome check src`, `tsc --noEmit`, `rsbuild build`); проверка: зелёный
run на ветке `feat/1-slice-0-skeleton`
- [ ] 6.2 Прогон DoD: `make up` (compose docs) → `make migrate` → `make seed-dev` → `make test` (включая `integration`) →
`curl /api/v1/me` с dev-токеном возвращает права; `openspec validate slice-0-skeleton --strict`; проверка: всё зелёное,
результаты вписаны в описание PR
- [ ] 6.3 Обновить `openspec/specs/` не требуется до архивации; дополнить `CLAUDE.md` (команды `make`), зафиксировать
новые допущения в `../docs/context/07-open-questions.md` через PR в docs (OQ-46 и всё, что всплыло); проверка:
ссылки из README/CLAUDE.md ведут на существующие файлы