Andrey Epifancev a0de203d88 chore: отметка задачи 22
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:48:55 +04:00

13 KiB
Raw Blame History

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 ведут на существующие файлы