Срез 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>
12 KiB
12 KiB
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 без учётных данных,/healthcheck200
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; проверка: без--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, получение 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-consumerarm-core-results(max_deliver5,ack_wait60 с); проверка: интеграционный тест против 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включены) + jobweb(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 ведут на существующие файлы