chore(openspec): рулинг R-19 (решения Task 12) в design.md

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-05 00:20:33 +04:00
parent 800f3a49c4
commit 6217e4e123

View File

@ -160,3 +160,4 @@ WHERE is_current`; `ALTER TYPE audit_action ADD VALUE` ×2 (`LEGAL_ENTITY_CHANGE
| 27 | 2026-10-04 | R-16 (SDD фаза 2, Task 9, приняты решения исполнителя): `errors` считает итоговые коды-пропуски; `GK_EXISTS` и для неизменной ГК (идемпотентность); РФ разрешается один раз на ГК после проверок пустого iD ГК и конфликта имени (нет «осиротевших» филиалов, `RF_CREATED = 53`); ИНН считается «встречавшимся» после проверок типа и формата; офис нового ЮЛ = действующий РФ ГК; ГСК/ГСЗ создаётся по флагу даже если у ЮЛ уже есть членство того же вида в другой группе (буквально §13.2, возможна пустая группа — T9.5); флаги обрабатываются и при `skip` (добавляется отсутствующее членство); значение колонки ГСК «входит в ГСЗ» → `UNKNOWN_FLAG`; xlsx читается с `reset_dimensions()`; пины `openpyxl==3.1.5`, `et_xmlfile==2.0.0`, `types-openpyxl` (dev) | единообразие со спекой и идемпотентность повторного импорта | каждое — локальная правка `plan_import` и теста |
| 28 | 2026-10-04 | R-17 (SDD фаза 2, Task 10, приняты решения исполнителя): общие `apply_group_update`/`apply_entity_update(session, audit, obj, changes: dict, *, now, description=None)` принимают словарь изменений (PATCH и импорт делят одну реализацию), `snapshot_entity`/`touch_entity` — функции модуля; обновления ГК/ЮЛ импортом пишут собственные события `GROUP_CHANGE`/`ASSIGNMENT_CHANGE`/`LEGAL_ENTITY_CHANGE` с `description="импорт ЕЦП"` помимо одного `DIRECTORY_IMPORT` (создания — только сводным событием); тест `dry_run` — на отдельной БД с зафиксированным seed, т.к. `session.rollback()` под SAVEPOINT-фикстурой `app` сносит seed теста; `IMPORT_MAX_BYTES` также в `.env.test`; имя файла сводится к basename (путь отбрасывается, пустое/`.`/`..` → `import`) для формата, `description` и ключа S3; `s3_object.sha256` заполняется, `content_type` — по расширению; сначала строка `s3_object` + flush, затем `put_object` (ошибка S3 откатывает импорт); разбор файла в `asyncio.to_thread`; `created_date` ГСК/ГСЗ не заполняется; 413 проверяется в эндпоинте (до чтения) и в сервисе (`HTTPException` из сервиса, как в брифе); архивные ГК входят в снимок и при `update` не разархивируются; сообщение `LE_IN_OTHER_GROUP` называет ЮЛ из другой ГК независимо от филиала пользователя (импорт — только `DIRECTORY_ADMIN`, права — данные) | единообразие истории/аудита ручной правки и импорта; честная проверка rollback; безопасность ключа S3 | пересмотр — правка сервиса/теста; ограничить раскрытие имён при выдаче `DIRECTORY_ADMIN` филиальному пользователю — убрать имя из сообщения |
| 29 | 2026-10-04 | R-18 (SDD фаза 2, Task 11, приняты решения исполнителя): сборка приложения в `tests/conftest.py` вынесена в приватные `_build_application`/`_client_factory`, добавлены фикстуры `committed_sessionmaker`/`committed_client`/`committed_app`/`committed_storage` (отдельная БД с зафиксированным seed — для тестов с явным `rollback`); `safe_filename`: убираются управляющие символы (Cc), имя ≤ 200 символов, суффикс ≤ 16 (иначе отбрасывается); замер ≤ 60 с — только HTTP-запрос полной выгрузки (факт 1,14 с) | не дублировать фабрику приложения; ключ S3 ≤ 1024 байт | косметика тестов; смена лимита имени — константа |
| 30 | 2026-10-04 | R-19 (SDD фаза 2, Task 12, приняты решения исполнителя): R-13 реализован иерархией — `RegionalOfficeBrief` (`/me`, поля `id,name,is_head_office`) и `RegionalOfficeOut(RegionalOfficeBrief)` (+`is_active`; справочник и `UserOut`), одно определение в `schemas/directories.py` (буквальный импорт добавил бы `is_active` в `/me`); несуществующий филиал в теле — 422 `OFFICE_NOT_FOUND` (как R-8), не 404; email хранится в форме `EmailStr` (домен в нижнем регистре), сравнение `strip().lower()`; `UserCreate` тоже `extra=forbid` (явный 422 на `external_id`); аудит создания содержит и `employee_no`; `USER_ROLE_CHANGE`: before — коды по алфавиту, after — в порядке запроса без дублей, повтор набора — без аудита; `UNKNOWN_ROLE`/`UNKNOWN_PERMISSION` несут список кодов; LAST_ADMIN сериализуется блокировкой `FOR NO KEY UPDATE` строки права `USERS_ADMIN` (+ строка пользователя/роли), порядок «объект → право» без дедлоков (`FOR UPDATE` дедлочит с FK-проверкой вставки аудита); LAST_ADMIN считает активных пользователей с активными ролями, несущими право (код роли не читается) | безопасность администрирования; `/me` не меняется | вернуть локальную модель в `me.py`; сменить код 422↔404 — одна проверка; снять блокировку — риск остаться без администратора при гонке |