Решили: запись в забег идёт через корневой роутер SessionCommandRouter (IRunCommands), а не через шину сессии напрямую. Шина (RunCommandBus) регистрируется в сеансе владельца только собой, так что дорога к записи одна для всех. Бой и мероприятие берут чтение (IRunStateView) и запись (IRunCommands) — оба типа существуют всегда, а сеанс под ними меняется.

Сказано: «Ты же ведь учла то что обсуждали. что Занятия могут быть вне сессии? Бои вне сейва (тестовые), отдельные быстрые PVP режимы вне сейва и т.д. Это учтено? Будет работать нормально?» (02.08.2026, сессия f24f0cc4). Вопрос вскрыл дефект, который переезд состояния только что внёс.

Почему: канон уровней требует прямо — «боевой скоуп собирается без RunState, без сети, без карты», потому что бой заказывают четверо, и трое из них живут без сейва (Ристалище, PvP, тест; плюс дев-арена). После переезда состояния в сессию BattleStartup и DeploymentController продолжали просить в конструкторе RunStateService и IRunCommands — то есть владельческую половину. Скоуп такого боя не поднялся бы вовсе: у гостя этих регистраций нет по построению, а вне сеанса их нет ни у кого. Код вызывающих при этом давно был готов к отсутствию (_commands?. и проверка результата) — не готов был контейнер.

Автосейв ушёл из расстановки в запись. _runStates.Autosave() стал _commands.RequestSave(): кто владелец сейва и есть ли он — не дело расстановки, а в коопе гость этим же вызовом попросит хоста. Реализация — RunCommandApplier.Save(): сохранение не команда и в лог не идёт, но трогает состояние, а туда ходят только оттуда.

Грабли: резолвить в роутере тот же интерфейс, который он собой и представляет, — рекурсия. SessionHost.Commands спрашивает у сеанса конкретный RunCommandBus: запрос IRunCommands ушёл бы вверх (в сессии своей регистрации нет) и вернулся бы к роутеру же.

Что осталось владельческим осознанно: мероприятие (ActRunner, RewardPresenter, ShopController, NodeResolver, EventEffectApplier) держит RunStateService напрямую. Это законно: забег ведёт владелец, а гость подключается к чужому мероприятию — его состав собирать кооп-вертикали. Бой же обязан собираться у всех, поэтому отвязан именно он.

Владелец правды: Game/Session/SessionCommandRouter.cs, Guild/Commands/IRunCommands.cs, тесты SessionScopeTests.BattleScope_NeverAsksForTheOwnerHalf и CommandRouter_AnswersNotWrittenWithoutASession.