Andrey Epifancev 43cbfe8e38 docs: change slice-0-skeleton под факты платформы — NATS без JetStream, Python 3.11, без CI и Nexus
Обмен с ядром на core NATS: подписка queue group, ack status=processing сразу по получении, process-local
LRU job_id против повторных запросов, публикация результата с повторами; удалены durable consumer / max_deliver /
ack_wait. Dockerfile python:3.11-slim; make check / make check-full вместо CI; raisa_* фильтруются при
dev-установке. Обновлены proposal, design (решения без обсуждения), specs, tasks, CLAUDE.md, openspec/config.yaml.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 16:21:42 +04:00

115 lines
13 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/`. Порядок — по зависимостям. Каждая задача ≤ половины
дня, с указанием файлов и способа проверки. Зависимости от других репозиториев: 5.1 ждёт контракт v1.0
(`docs` задачи 1.1–1.4); 2.x нужна инфраструктура compose (`docs` 2.1). CI нет — проверка каждой задачи локальная.
## 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; `Makefile`: `venv` (python3.11, `requirements.txt` без строк `raisa_*`,
затем dev), `lint`, `test`, `test-integration`, `check`, `check-full`, `up`, `migrate`, `seed`, `seed-dev`;
проверка: `make venv && make lint` зелёные на текущем каркасе без доступа к Nexus
- [ ] 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`, `OUTBOX_POLL_SECONDS=5`, `ACK_TIMEOUT_SECONDS=30`,
`OUTBOX_MAX_REPUBLISH=3`; `.env.example`, `.env.test`; проверка: `ENV_FILE=.env.test python -c "from src.core.config
import settings"` читает тестовые значения
- [ ] 1.3 `Dockerfile` (`python:3.11-slim`, установка `requirements.txt` без `raisa_*`, `CMD python run_server.py`), убрать
строку `Dockerfile` из `.gitignore`; `web/package.json`: `raisa-ui`, `raisa-fastapi-protected-web` →
`optionalDependencies`; проверка: `docker build -t arm-core .` успешен без Nexus; `npm install` в `web/` проходит,
`npm run build` собирает
- [ ] 1.4 `src/app.py`: NATS — пустые `NATS_SEED/NATS_JWT` → подключение без nkeys (перенести `nkeys.from_seed` из
модуля в функцию), подключение при `NATS_ENABLED=true` независимо от `DEBUG`, `/healthcheck` отдаёт `nats`
(`ok`/`down`/`off`); `api/router.py` — `APIRouter(prefix="/api/v1")`; проверка: `python run_server.py` стартует
против compose без учётных данных, `/healthcheck` 200 с `nats = ok`
## 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 16;
`alembic revision --autogenerate` после — пустая ревизия
- [ ] 2.7 `tests/conftest.py`: engine из `.env.test`, миграции один раз на сессию, транзакция с откатом на тест, маркеры
`integration`/`fixtures` (skip с причиной при отсутствии `ARM_KA_FIXTURES`); проверка: тест чтения `Decimal` из
`osv_entry.closing_debit` проходит; `make check` зелёный
## 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, работа без Nexus,
получение dev-токена, запуск против compose; проверка: новый разработчик проходит README до зелёного `/me`
## 5. Обмен через `recognition_job` и аудит (spec `recognition-job-exchange`, `audit-trail`)
- [ ] 5.1 `api/contracts/` — копии трёх схем из `../docs/contracts/` + `examples/`; `src/contract/` — pydantic-модели
запроса, конверта (статусы `processing | done | needs_check | error`), файла результата (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 или без; автопереподключение), `publish(subject, payload, msg_id)`
с заголовком `Nats-Msg-Id`, подписка `recognition.result` в queue group `arm-core`; проверка: интеграционный тест
против compose-NATS (без JetStream) — публикация и получение сообщения с заголовком; два подписчика в одной группе
получают сообщение один раз
- [ ] 5.4 `src/services/recognition_exchange.py`: `publish_pending()` (SKIP LOCKED; первая публикация `queued →
published`; повторная публикация `published` без ack через `ACK_TIMEOUT_SECONDS` с тем же `Nats-Msg-Id`,
`metrics.republish_count`; после `OUTBOX_MAX_REPUBLISH` → `dlq`, `error_code = NO_ACK`), `expire_deadlines()`;
фоновые задачи в lifespan при `NATS_ENABLED`; проверка: интеграционный тест — вставка `s3_object` + `stored_file` +
`recognition_job(queued)` → сообщение получено тестовым подписчиком и проходит схему → строка `published`; без ack
(с укороченными таймаутами) — три повтора с одним `Nats-Msg-Id` и `dlq`; задание с прошедшим `reply_deadline` → `timeout`
- [ ] 5.5 `handle_message()`: ack `processing` → `processing`/`started_at`/`service_*`; результат → `ResultMapper`/`NoopMapper`
+ регистрация `s3_object` + обновление задания; дубликат по `result_msg_id`, неизвестный `job_id`, результат для
финального статуса — лог без изменений; проверка: интеграционный тест полного цикла queued → published → processing
→ done с `s3_object`; повтор не меняет строки; `error NOT_IMPLEMENTED` → `error_code`; `done` после `timeout` игнорируется
## 6. Проверки и завершение
- [ ] 6.1 `make check-full`: `make check` + `pytest -m integration` + цикл миграций + `check_dbml_vs_orm` + `docker build`
+ `web`: `npm install`, `npx biome check src`, `npx tsc --noEmit`, `npm run build`; проверка: цель зелёная против
compose docs на ветке `feat/1-slice-0-skeleton`
- [ ] 6.2 Прогон DoD: `make up` (compose docs) → `make migrate` → `make seed-dev` → `make check-full` → `curl /api/v1/me`
с dev-токеном возвращает права; `openspec validate slice-0-skeleton --strict`; проверка: всё зелёное, вывод
`make check-full` вписан в описание PR
- [ ] 6.3 Дополнить `CLAUDE.md` (команды `make`), зафиксировать новые допущения в `../docs/context/07-open-questions.md`
через PR в docs (OQ-46 и всё, что всплыло); проверка: ссылки из README/CLAUDE.md ведут на существующие файлы