From 811a1d2e90cee154ccff348a2439069158fca8c7 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 5 Oct 2026 02:02:43 +0400 Subject: [PATCH] =?UTF-8?q?chore(openspec):=20=D1=80=D1=83=D0=BB=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3=20R-22=20(=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20=D0=BC?= =?UTF-8?q?=D0=B0=D0=BF=D0=BF=D0=B8=D0=BD=D0=B3=20=D1=83=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D0=B9)=20=D0=B2=20design.m?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- openspec/changes/slice-1-directories/design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/openspec/changes/slice-1-directories/design.md b/openspec/changes/slice-1-directories/design.md index 64b6b23..8009814 100644 --- a/openspec/changes/slice-1-directories/design.md +++ b/openspec/changes/slice-1-directories/design.md @@ -164,3 +164,4 @@ WHERE is_current`; `ALTER TYPE audit_action ADD VALUE` ×2 (`LEGAL_ENTITY_CHANGE | 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 — одна проверка; снять блокировку — риск остаться без администратора при гонке | | 31 | 2026-10-04 | R-20 (SDD фаза 2, Task 13, приняты решения исполнителя): условие dev-входа — литерал `process.env.NODE_ENV !== 'production'` прямо в `DevLogin` (rsbuild подменяет `process.env` объектом `REACT_APP_*`, через `Config` `NODE_ENV` недоступен; в prod-бандле формы и `dev/token` нет — проверено grep); `AuthGate` оборачивает весь `Layout` (а не только `Routes`), чтобы форма входа не рисовалась рядом с сайдбаром; профиль детям — `MeContext`/`useCurrentUser`; Redux Provider каркаса не удалён; токен: `sessionStorage` с запасным токеном в памяти при недоступном хранилище; клиент: сетевые ошибки → `ApiError(0,'NETWORK')`, не-JSON 2xx → `BAD_RESPONSE`; в prod ошибка `/me` показывается в `Alert` с кнопкой «Повторить»; типы `Permission.object_type/action` — union по enum бэкенда | D-9, безопасность prod-сборки, устойчивость клиента | вернуть `AuthGate` вокруг `Routes`; при смене сборщика пересмотреть условие `NODE_ENV` | | 32 | 2026-10-04 | R-21 (SDD фаза 2, Task 14, приняты решения исполнителя): `/admin` берёт профиль из `useCurrentUser()` (контекст `AuthGate`), а не `useMe()` (второго `/me` нет); пункт меню «Пользователи и роли» скрыт без `USERS_ADMIN` (страница по URL всё равно закрыта 403 без запросов к `/admin/*`); корень приложения обёрнут в antd `` (`index.tsx`, вне списка файлов брифа) — уведомления через `App.useApp().message`; общие хуки (`useNotifyError`/`useMountedRef`/`useAsyncData`) — `widgets/Admin/hooks.ts`, константа `USERS_ADMIN` — `auth/permissions.ts`; строки таблицы показывают серверное состояние (без оптимистичных обновлений), при ошибке список перечитывается, на время сохранения блокируются контролы всех строк; матрица: PUT по ролям строго последовательно (защита от гонки снятий `USERS_ADMIN`), после сохранения — слияние ответов, сброс черновика, перечитывание; неактивную роль нельзя назначить, но можно снять; черновик матрицы — `Map` (lib ES2020 без `Object.hasOwn`); `EMAIL_EXISTS` показывается под полем email; страница показывает первые 200 пользователей с пометкой (поиска/пагинации нет) | D-9 минимализм; безопасность и устойчивость экрана | добавить поиск/пагинацию при росте числа пользователей; расширить lib и заменить `Map` | +| 33 | 2026-10-04 | R-22 (SDD фаза 2, пост-Task 15): общий маппинг нарушений уникальности PostgreSQL (SQLSTATE 23505) в коды 409 — реестр `src/common/constraint_conflicts.py` (7 ограничений: crm_id ГК и ЮЛ, inn, алиас, email (точный дубликат), два индекса членства); имя ограничения берётся из `orig.__cause__.constraint_name` (asyncpg) / `.diag` (psycopg), текст ошибки не разбирается; ответ — тот же `{"detail": {"code","message"}}`, что у pre-check, без значений ключей; прочие ошибки целостности — прежняя строка; в лог добавлен `resolved_code`; гонка повторного добавления ЮЛ в ту же группу даёт `MEMBER_OF_OTHER_GROUP` (срабатывает индекс «ЮЛ + вид»), текст нейтральный; без кода остаются `ALIAS_TAKEN`, email в другом регистре, `external_id` | закрывает T6.5, T7.1, T8.2, T12.7: гонка не отличается от последовательного дубля | расширить реестр; убрать — вернуть общий 409 |