При непустой `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
`_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
Двухфазный загрузчик: валидация yaml целиком до записи (SeedError), затем upsert по
натуральному ключу без удалений; run_seed(session, *, dev, seed_dir) без commit, CLI коммитит.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>