DM как полный демиург
DM может что угодно. Объясняет, почему права DM не ограничиваются guardrail'ами, как обеспечивается safety через следы, и что именно DM не может технически.
DM — это Бог кампании. Вставить NPC из ниоткуда, откатить решение игрока, поменять цены в лавке задним числом, удалить персонажа — всё это рабочие операции. Ограничивать DM правилами системы значит создавать систему, которая мешает вести игру.
Safety обеспечивается не через запреты, а через аудит и следы.
Как устроены права DM сейчас
Все server actions в app/actions/*.ts используют createAdminClient()
(service role) для записи — это bypass RLS. RLS применяется к прямым
клиентским чтениям из lib/queries/*, но пути записи идут как admin.
Поэтому каждый server action начинается с auth check:
resolveAuth(campaignId)— возвращает{ role }. Еслиroleнеdmилиowner— action возвращает ошибку без записи.getMembership(campaignId)— базовая проверка «является ли участником».canEditNode(nodeId, campaignId, userId, role)— per-node гейтинг, зеркалит RLS-политикуcan_edit_node.
RLS на таблицах — safety net на случай, если route handler забыл свой гейт и использует user client. Это второй рубеж, а не первый.
Что DM может
Практически всё, что касается контента кампании:
- Создать, отредактировать, удалить любую ноду — NPC, локацию, предмет,
сессию, петлю. Через
can_edit_node, который для DM всегдаtrue. - Записать транзакцию за любого PC — деньги, предметы, переводы.
- Подтвердить или отклонить заявки игроков (
status→approved/rejected). - Управлять составом кампании — пригласить, повысить, удалить участника
(
/c/[slug]/members). - Редактировать PC любого игрока — через
is_dm_or_owner(campaign_id).
Что DM не может
Четыре жёстких ограничения на уровне системы:
- Уронить базу.
DROP TABLE,TRUNCATE, DDL — недоступны. Суперuser — только у DevOps, не у приложения. - Читать или писать чужие кампании.
campaign_idпроверяется на каждом уровне; is_member / is_dm_or_owner — SECURITY DEFINER функции. - Писать в
auth.users. Auth — зона Supabase; приложение работает только сuser_profilesиcampaign_members. - Удалить собственную кампанию через стандартный UI — мягкая защита
(нет кнопки), а не техническое ограничение. Каскадное удаление в схеме
есть (
ON DELETE CASCADE), но через admin action, которой не существует.
Safety layer: следы вместо запретов
В проде сейчас audit log и soft delete отсутствуют. DM правит напрямую; hard DELETE на чувствительных таблицах возможен.
Целевая архитектура:
event.deleted_at— nullable timestamp вместоDELETE. Удалённое остаётся в БД с меткой, фильтруется на уровне RLS/queries.- Версионирование контента —
item_version,monster_version: правка создаёт новую версию, старые остаются; события ссылаются на конкретную версию. dm_audit_log— каждая DM-операция пишется сaction_type,target_id,before_state,after_state,timestamp. Читается толькоowner.
→ roadmap/audit-log-and-safety.md
Разделение DM-операций в целевой модели
В HANDOFF DM-операции — именованные типы событий:
| Event type | Смысл |
|---|---|
dm_overwrite | Замена значения (позиция NPC, погода, данные локации) |
dm_inject | Вставка сущности (новый NPC, предмет, событие) |
dm_correction | Коррекция прошлого события с обоснованием |
Сейчас DM пишет напрямую в таблицы через server actions без этих типов —
универсального events лога ещё нет.
Принцип: полная власть + полный аудит. DM — демиург, но не анонимный.
→
roles-and-clients.md— чем DM отличается от player и owner в системе ролей. →tool-first.md— почему инструмент работает на DM, а не ограничивает его.