Обмен с ядром на 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>
13 KiB
13 KiB
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 без учётных данных,/healthcheck200 с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; проверка: без--devapp_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 grouparm-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(): ackprocessing→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 ведут на существующие файлы