# audit-trail Specification ## Purpose Все действия из enum `audit_action` пишутся в `audit_event` одним способом — через сервис аудита; журнал только дополняется, записи неизменяемы (ТЗ 8.2). ## Requirements ### Requirement: Единая точка записи Сервис аудита SHALL предоставлять метод записи события с полями `action` (из enum `audit_action`), `section` (из `audit_section`), `object_type`, `object_id`, `user_id` (NULL для системных действий), `consolidation_version_id`, `description`, `before_value`, `after_value` (jsonb), `comment`, `result`, `changed_field`, `ip_address`, `user_agent`; `occurred_at` SHALL проставляться сервером (UTC). Запись SHALL выполняться в той же транзакции, что и изменение данных. NOTE: В Срезе 0 обмен через recognition_job аудит не пишет (рулинг R-7, design #20); сервис готов к использованию Срезом 2+. #### Scenario: Системное событие - **WHEN** системный процесс (без пользователя) вызывает сервис аудита - **THEN** в `audit_event` появляется строка с `user_id = NULL`, `before_value`/`after_value` содержат старый и новый статус #### Scenario: Откат транзакции - **WHEN** изменение данных и запись аудита выполняются в одной транзакции, и она откатывается - **THEN** строка `audit_event` не сохраняется ### Requirement: Журнал только дополняется ORM-модель `audit_event` MUST NOT использоваться для `UPDATE` или `DELETE` в коде ядра; тест SHALL проверять, что в `src/` нет обращений, изменяющих или удаляющих `audit_event`, а сервис аудита не имеет методов изменения. #### Scenario: Попытка изменить запись - **WHEN** код вызывает обновление строки `audit_event` через сервис аудита - **THEN** такого метода нет (ошибка на этапе типизации/теста), запись остаётся прежней ### Requirement: События справочников, импорта и администрирования Каждая мутация справочников, импорт и каждое действие администратора SHALL писать `audit_event` через сервис аудита в той же транзакции с `user_id` инициатора, `ip_address` и `user_agent` запроса: | Действие | `action` | `section` | `object_type` | |---|---|---|---| | создание/изменение/архив ГК | `GROUP_CHANGE` | `DIRECTORIES` | `company_group` | | смена филиала ГК | `ASSIGNMENT_CHANGE` | `DIRECTORIES` | `company_group` | | создание/изменение/перевод/закрытие ЮЛ, алиасы | `LEGAL_ENTITY_CHANGE` | `DIRECTORIES` | `legal_entity` / `legal_entity_alias` | | ГСК/ГСЗ и их состав | `MEMBERSHIP_CHANGE` | `DIRECTORIES` | `related_group` / `related_group_member` | | импорт выгрузки | `DIRECTORY_IMPORT` | `DIRECTORIES` | `directory_import` | | пользователь создан/изменён/деактивирован | `USER_CHANGE` | `USERS` | `app_user` | | роли пользователя | `USER_ROLE_CHANGE` | `USERS` | `app_user` | | права роли | `ROLE_PERMISSION_CHANGE` | `ACCESS` | `role` | `before_value`/`after_value` SHALL содержать изменённые поля (для списков — весь набор до и после); `changed_field` — имя поля при изменении одного поля. #### Scenario: Изменение имени ГК - **WHEN** имя ГК меняется с «А» на «Б» - **THEN** в `audit_event` строка `GROUP_CHANGE` с `changed_field = "name"`, `before_value = {"name": "А"}`, `after_value = {"name": "Б"}`, `user_id` инициатора #### Scenario: Откат мутации справочника - **WHEN** транзакция перевода ЮЛ откатывается - **THEN** строки `LEGAL_ENTITY_CHANGE` и `MEMBERSHIP_CHANGE` в `audit_event` отсутствуют