- reference-data-seed: матрица role_permission целиком — только в пустую
таблицу, в заполненную — лишь пары ролей/прав, вставленных тем же запуском
(сценарии: снятое право, новые роль и право, все связи сняты); `--dev` только
при DEBUG=true (код 2 до обращения к БД).
- audit-trail: требование «События входа» (LOGIN/LOGIN_FAILED, раздел SESSION)
— исключение из «той же транзакции» с обоснованием, обрезка полей, доверенные
прокси; строки в таблице событий.
- authn-authz: ссылка на аудит входа; требование «Прод-конфигурация
HTTP-поверхности» — /docs, /redoc, /openapi.json только при DEBUG, CORS по
CORS_ORIGINS, 405 на неверный метод, 404 под /api/v1, GET / без редиректа.
`openspec validate --specs`: 9 passed, 0 failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Формулировка уточнена в докстрингах `services/session_audit.py` и
`api/dependencies/auth.py`, README «Безопасность dev-режима» и CLAUDE.md; тест
событий входа дополнен запросом со схемой Basic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`*` вместе с credentials спецификация CORS запрещает (Starlette тогда отражает
Origin запроса); фронт передаёт токен заголовком Authorization, cookie не
нужны. README «Конфигурация» уточнён.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
При непустой `role_permission` вставляются пары, у которых роль или право
вставлены этим же прогоном (`_upsert` возвращает вставленные ключи): их
администратор снять не мог. Прочие пары по-прежнему не трогаются — снятые
права не возвращаются. Убрана мёртвая ветка `unchanged` (R-9): при пустой
таблице совпадений быть не может, при заполненной сверка не нужна.
Тесты: новое право и новая роль во временном seed-каталоге → повторный seed
выдаёт только их пары, снятая связь не возвращается; администратор снял все
связи → матрица заливается заново. README, CLAUDE.md, комментарий
`role_permissions.yaml` и докстринг `seed.py` уточнены.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`ProxyHeadersMiddleware(trusted_hosts=settings.FORWARDED_ALLOW_IPS)` вместо
`["*"]`: по умолчанию `127.0.0.1`, список через запятую или `*`. Иначе любой
клиент подставлял свой адрес в `audit_event.ip_address` (в том числе в
LOGIN_FAILED). `.env.example`/`.env.test`, README «Конфигурация» и
«Безопасность dev-режима» дополнены; compose `docs/dev` не менялся.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`clip` в `services/session_audit.py` применяется в `record_login` (общая точка
LOGIN/LOGIN_FAILED): заголовок User-Agent и email из токена присылает клиент,
без ограничения запись в журнал была бы произвольной длины. Тесты: помощник,
запись `SessionAudit` без БД и длинный User-Agent при 401 через API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`_apply_role_permissions` (`scripts/seed_lib/apply.py`) загружает матрицу из
`role_permissions.yaml` только при пустой `role_permission`; иначе шаг
пропускается целиком с причиной в отчёте (`TableStat.skipped_reason`,
«role_permission: пропущено — …»). Права, снятые администратором, не
возвращаются; `role` и `permission` по-прежнему upsert'ятся.
Тесты: повторный прогон — «пропущено», матрица та же; снятая связь после
повторного seed не возвращается. README «Администрирование» и раздел seed
переписаны под новое поведение.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`_today()` в `services/legal_entities.py` берёт московскую дату
(`common/clock.py`: `moscow_date`, `MOSCOW_TZ` = Europe/Moscow; без базы
часовых поясов — фиксированный UTC+3, Москва без перехода на летнее время с
2014 г.). Тесты на границе суток: 23:30 МСК (20:30 UTC) — ещё 4-е, 00:30 МСК
(21:30 UTC того же дня) — уже 5-е, и 4-е становится прошлым. README: даты
перевода/вывода — по Москве.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Раздел SESSION, object_type app_user, IP и user-agent запроса
(`services/session_audit.py`):
- LOGIN — выдача dev-токена (в транзакции запроса) и первый успешный
`current_user` пользователя после старта процесса (`SessionAudit.logged_in`,
в памяти процесса; параллельные первые запросы — одно событие);
- LOGIN_FAILED — 401 (токен не принят: user_id пуст, причина без токена) и 403
аутентификации (не зарегистрирован / отключён / неоднозначен: email в
description, user_id — если найден). `AuthenticationFailedError` (подкласс
HTTPException) обрабатывается после отката транзакции запроса, запись — в
отдельной транзакции `session_transaction`, поэтому остаётся в журнале.
Без заголовка Authorization и 403 «недостаточно прав» — не события входа;
ошибка записи аудита входа ответ не меняет.
Тесты: `test_login_audit.py` (в т.ч. фиксация при откате запроса на
`committed_app`); счётчики следов импорта не учитывают события SESSION.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
`api/.env.test`, `api/.env.example` и прочие env-файлы больше не попадают в
образ (как у tabular-parser и scan-gateway): настройки контейнера задаются
переменными окружения.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Причина редиректа: `FastAPI(root_path="/")` — путь маршрута для `GET /` после
вычитания root_path пуст, монтирование SPA его не находит, и роутер Starlette
отвечал 307 на `//`. Теперь `root_path` нормализуется (`/` → "", хвостовой `/`
снимается) — `_normalize_root_path` существовал, но не применялся.
`SPAStaticFiles` для путей `api/...` сверяется с маршрутами приложения: путь
совпал, метод нет (`Match.PARTIAL`) — 405 с заголовком `Allow` и русским
`detail`; иначе — прежний 404 (dev-маршруты при выключенных флагах — 404).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Приложение собирает `create_app(settings, web_dir=...)`: при DEBUG=false нет
`/docs`, `/redoc`, `/openapi.json` (SPA-заглушка на эти пути отвечает 404, а не
index.html); CORS — `cors_options`: DEBUG — `*` с credentials, иначе
`CORS_ORIGINS` (пусто — middleware не подключается, credentials только для
списка без `*`). Настройка `CORS_ORIGINS` читает JSON-список или строку через
запятую. `.env.example` и README «Конфигурация» дополнены.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
CLI `python -m scripts.seed --dev` сверяется с `DEBUG` (`check_dev_allowed`) до
чтения seed и подключения к БД; при `DEBUG=false` — код возврата 2 и сообщение
«--dev доступен только при DEBUG=true (dev-пользователи не для прода)».
`run_seed(session, dev=True)` защиты не имеет (её вызывают тесты).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2