33 KiB
Phase 1: Matrix QA & Polish — Research
Researched: 2026-04-02 Domain: matrix-nio AsyncClient — Space+rooms architecture, OutgoingUI text rendering, !yes/!no confirmation flow Confidence: HIGH (all critical APIs verified against the installed library)
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- D-01: Space+rooms — единственная поддерживаемая модель. DM-first убрать.
- D-02: При первом invite бот создаёт Space
Lambda — {display_name}, внутри — первую комнатуЧат 1. Приглашает пользователя. - D-03:
!new [name]создаёт новую комнату внутри Space пользователя, приглашает его туда. - D-04:
!archiveвыводит комнату из Space (не удаляет). - D-05: Маппинг
room_id → space_id+room_id → chat_idхранится в SQLite черезadapter/matrix/store.py. - D-06: Реакции (
👍/❌) — убрать полностью.OutgoingUIс кнопками рендерится как текст +!yes/!no. - D-07: Когда агент запрашивает подтверждение, бот пишет текстовое сообщение с описанием и подсказкой:
Ответьте !yes для подтверждения или !no для отмены. - D-08:
!yes/!noработают в текущей комнате. Состояние ожидания подтверждения хранится per (user_id, room_id). - D-09: Все команды работают из любой комнаты Space — нет выделенной комнаты «Настройки».
- D-10: Команды:
!new [name],!chats,!rename <name>,!archive,!skills,!soul,!safety,!settings,!yes,!no. - D-11:
!start— не нужен, онбординг через invite flow. - D-12:
!settings— read-only дашборд: одно сообщение со статусом всего (скиллы, soul, safety, активные чаты). Ничего не меняет. - D-13: Изменения через субкоманды:
!skills,!skill on/off <name>,!soul,!soul name/style/priority/reset <value>,!safety,!safety on/off <action>. - D-14: Каждая команда без аргументов (
!skills,!soul,!safety) выводит текущее состояние + подсказку как менять.
Claude's Discretion
- Формат текста в Matrix (plain text vs markdown) — на усмотрение, Matrix клиенты рендерят markdown
- Структура invite-сообщения в новом Space — на усмотрение, главное: приветствие + список команд
- Обработка ошибок matrix-nio (room_create fail, join fail) — логировать + ответить пользователю
Deferred Ideas (OUT OF SCOPE)
- Комната «Настройки» как отдельная закреплённая комната — решили не делать в Phase 1
- E2EE / python-olm — инфраструктурный трек, вне scope
- Space discovery (бот находит существующий Space при повторном invite) — Phase 2+
- Attachment handling (m.file, m.image, m.audio) — Phase 2+ </user_constraints>
Summary
Phase 1 переписывает Matrix адаптер с DM-first на Space+rooms модель, убирает реакции в пользу !yes/!no, и реализует все команды управления. Большая часть бизнес-логики уже работает через core/handlers/ и adapter/matrix/handlers/settings.py. Главная работа — в трёх точках: handle_invite (создание Space + двух комнат), make_handle_new_chat (добавление комнаты в Space), и send_outgoing (убрать реакции, добавить pending-state для !yes/!no).
Текущее состояние: 97 тестов зелёные. Для "96+ зелёных" после рефакторинга нужно обновить 3 существующих теста (они проверяют DM-поведение и реакции) и добавить ~12 новых тестов на Space-сценарии. Итого целевой range — 106–110 тестов.
Критическая деталь: AsyncClient.room_create принимает space=True (булевый параметр, не room_type="m.space") для создания Space. Добавление дочерней комнаты — через room_put_state на Space с event_type m.space.child и state_key = child room_id. Это проверено против установленной версии matrix-nio.
Primary recommendation: Реализовать в трёх независимых задачах Codex: (1) invite flow — Space+rooms creation, (2) send_outgoing — убрать реакции, добавить pending-confirm store, (3) обновить тесты под новое поведение.
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| matrix-nio | установлена (проверено: space=True параметр присутствует) |
Matrix async клиент — room_create, room_put_state, room_invite, join | Единственный maintained async Python Matrix клиент |
| structlog | уже используется | Логирование | Уже в проекте |
| pytest-asyncio | уже используется | Async тесты | Уже в проекте |
Версию matrix-nio не нужно менять. Установленная версия поддерживает space=True в room_create и room_put_state для state events.
Architecture Patterns
Паттерн 1: Создание Space + первой комнаты (invite flow)
Что: При первом invite бот делает 5 последовательных API вызовов — создание Space, создание chat-комнаты, линковка child→Space, приглашение пользователя в обе, запись в store.
Verified API (из installed matrix-nio):
# 1. Создать Space
space_resp = await client.room_create(
name=f"Lambda — {display_name}",
space=True, # <-- булевый флаг, не room_type
visibility="private",
is_direct=False,
)
# space_resp.room_id — строка
# 2. Создать первую chat-комнату
chat_resp = await client.room_create(
name="Чат 1",
visibility="private",
is_direct=False,
)
# chat_resp.room_id — строка
# 3. Добавить комнату в Space как child
# state_key = room_id дочерней комнаты
await client.room_put_state(
room_id=space_resp.room_id,
event_type="m.space.child",
content={
"via": [homeserver_domain], # например "matrix.org"
},
state_key=chat_resp.room_id,
)
# 4. Пригласить пользователя в Space и в chat-комнату
await client.room_invite(space_resp.room_id, matrix_user_id)
await client.room_invite(chat_resp.room_id, matrix_user_id)
# 5. Записать в store
await set_user_meta(store, matrix_user_id, {
"space_id": space_resp.room_id,
"next_chat_index": 2, # C1 уже занят
})
await set_room_meta(store, chat_resp.room_id, {
"room_type": "chat",
"chat_id": "C1",
"display_name": "Чат 1",
"matrix_user_id": matrix_user_id,
"space_id": space_resp.room_id,
})
Важный gotcha: Бот сам не вступает в Space (join). Он создаёт Space как владелец, поэтому уже является членом. join нужен только для входящей DM-комнаты (invite в существующую комнату). В новом flow: бот создаёт комнаты сам, поэтому join для Space и chat-комнаты не нужен.
Паттерн 2: Добавление новой комнаты (!new)
async def handle_new_chat(...):
user_meta = await get_user_meta(store, event.user_id) or {}
space_id = user_meta.get("space_id")
if not space_id:
# Пользователь не прошёл invite flow — не должно случиться, но guard нужен
return [OutgoingMessage(chat_id=event.chat_id, text="Ошибка: Space не найден.")]
chat_id = await next_chat_id(store, event.user_id)
room_name = " ".join(event.args).strip() or f"Чат {chat_id}"
resp = await client.room_create(name=room_name, visibility="private", is_direct=False)
room_id = resp.room_id
homeserver = event.user_id.split(":")[1] # "@user:matrix.org" → "matrix.org"
await client.room_put_state(
room_id=space_id,
event_type="m.space.child",
content={"via": [homeserver]},
state_key=room_id,
)
await client.room_invite(room_id, event.user_id)
await set_room_meta(store, room_id, {
"room_type": "chat",
"chat_id": chat_id,
"display_name": room_name,
"matrix_user_id": event.user_id,
"space_id": space_id,
})
Паттерн 3: Archive (!archive) — убрать из Space
# Убрать child: поставить пустой content (или content без 'via')
# Matrix spec: отправить m.space.child с пустым {} или без 'via' удаляет связь
await client.room_put_state(
room_id=space_id,
event_type="m.space.child",
content={}, # пустой content = удалить child relationship
state_key=room_id, # room_id архивируемой комнаты
)
Confidence: MEDIUM — Matrix spec говорит что пустой content убирает child, но поведение Element может варьироваться. Альтернатива: оставить room_put_state с {"via": []} (пустой массив).
Паттерн 4: OutgoingUI → текст + !yes/!no (без реакций)
Что убрать:
_button_action_to_reactionвbot.py— удалить целиком- Блок
for button in event.buttons: reaction = _button_action_to_reaction(...)— удалить ReactionEventcallback (on_reaction+client.add_event_callback) — удалитьfrom_reactionв converter — оставить (используется для skill-reactions), но skill-reaction инфраструктура тоже под вопросом (D-06 убирает реакции полностью)
Что добавить в send_outgoing для OutgoingUI:
if isinstance(event, OutgoingUI):
lines = [event.text, ""]
for button in event.buttons:
lines.append(f"• {button.label}")
lines += ["", "Ответьте !yes для подтверждения или !no для отмены."]
body = "\n".join(lines)
await client.room_send(room_id, "m.room.message", {"msgtype": "m.text", "body": body})
# Сохранить pending state per (user_id, room_id)
await set_pending_confirm(store, user_id=???, room_id=room_id, action_id=???)
Проблема: send_outgoing сейчас не знает user_id — только room_id. Для сохранения pending state нужен либо рефакторинг сигнатуры, либо хранение pending по room_id (без user_id — достаточно, т.к. room_id уникален для конкретного пользователя в Space модели).
Паттерн 5: Pending confirm state
# Новые helpers в adapter/matrix/store.py
PENDING_CONFIRM_PREFIX = "matrix_pending_confirm:"
async def get_pending_confirm(store, room_id: str) -> dict | None:
return await store.get(f"{PENDING_CONFIRM_PREFIX}{room_id}")
async def set_pending_confirm(store, room_id: str, meta: dict) -> None:
await store.set(f"{PENDING_CONFIRM_PREFIX}{room_id}", meta)
async def clear_pending_confirm(store, room_id: str) -> None:
await store.delete(f"{PENDING_CONFIRM_PREFIX}{room_id}")
!yes/!no уже конвертируются в IncomingCallback(action="confirm"/"cancel") в converter.py. Нужно обновить handle_confirm/handle_cancel в adapter/matrix/handlers/confirm.py чтобы читать pending state и возвращать осмысленный ответ.
Паттерн 6: Hardcoded "C1" bug fix
# auth.py:27 — СЕЙЧАС (баг):
"chat_id": "C1"
# ДОЛЖНО БЫТЬ:
chat_id = await next_chat_id(store, matrix_user_id) # возвращает "C1" для первого пользователя
next_chat_id уже существует в store.py и правильно инкрементирует per-user. Нужно просто использовать его в handle_invite вместо хардкода.
Рекомендуемая структура store после рефакторинга
Текущие ключи в store:
matrix_room:{room_id}→{room_type, chat_id, display_name, matrix_user_id}— добавитьspace_idmatrix_user:{user_id}→{next_chat_index, ...}— добавитьspace_idmatrix_state:{room_id}→{state}— оставить как естьmatrix_skills_msg:{room_id}→{event_id}— оставить (или убрать если реакции полностью уходят)
Новые ключи:
matrix_pending_confirm:{room_id}→{action_id, description, expires_at}— для !yes/!no
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| Space creation | Кастомный HTTP запрос к Matrix API | AsyncClient.room_create(space=True) |
Встроено в matrix-nio, управляет session state |
| Adding child room to Space | Кастомный state event builder | AsyncClient.room_put_state(room_id, "m.space.child", ...) |
Правильный Content-Type, auth headers автоматически |
| User invite | Прямой HTTP PUT | AsyncClient.room_invite(room_id, user_id) |
Обрабатывает ошибки M_FORBIDDEN, already-joined |
| Error detection | Проверка статус-кодов | isinstance(resp, RoomCreateError) / isinstance(resp, RoomPutStateError) |
matrix-nio возвращает типизированные error-объекты |
Common Pitfalls
Pitfall 1: room_create(space=True) vs room_type="m.space"
What goes wrong: Передача room_type="m.space" как отдельный параметр — работает, но space=True — это удобный shortcut в matrix-nio, который внутри устанавливает тот же room_type. Оба варианта корректны, но space=True проще читается.
Проверено: room_create signature в installed matrix-nio имеет space: bool = False. Нет отдельного is_space параметра.
How to avoid: Использовать space=True, не room_type="m.space".
Pitfall 2: room_id из RoomCreateResponse — не getattr
What goes wrong: Текущий код в handlers/chat.py:55: room_id = getattr(response, "room_id", None). Это работает для RoomCreateResponse, но молча возвращает None если пришёл RoomCreateError (у которого нет room_id).
How to avoid:
from nio.responses import RoomCreateError
resp = await client.room_create(...)
if isinstance(resp, RoomCreateError):
logger.error("room_create failed", status_code=resp.status_code)
return [OutgoingMessage(..., text="Не удалось создать комнату.")]
room_id = resp.room_id # прямой доступ, не getattr
Pitfall 3: m.space.child — state_key это room_id дочерней комнаты, не пустая строка
What goes wrong: room_put_state по умолчанию state_key="". Для m.space.child state_key ДОЛЖЕН быть room_id дочерней комнаты — иначе Space создастся некорректно.
How to avoid: Всегда передавать state_key=child_room_id явно.
Pitfall 4: Бот должен быть в Space чтобы добавлять children
What goes wrong: Бот создаёт Space (становится владельцем), потом пытается сделать room_put_state на Space. Это работает т.к. создатель автоматически имеет power level 100. Но если бот потерял membership (kicked out), room_put_state вернёт M_FORBIDDEN.
How to avoid: Логировать ошибку и сообщать пользователю. Не ретраить молча.
Pitfall 5: Дублирование invite flow (идемпотентность)
What goes wrong: Текущий handle_invite проверяет get_room_meta(store, room.room_id) чтобы не запускать flow дважды. После рефакторинга на Space+rooms нужно проверять get_user_meta(store, matrix_user_id) — потому что invite может прийти повторно в разные комнаты Space, а Space создаётся один раз per user.
How to avoid: Idempotency check переносится на уровень user_meta: if user_meta.get("space_id"): return.
Pitfall 6: skills_message реакции — остаток от старого UX
What goes wrong: adapter/matrix/reactions.py и build_skills_text до сих пор рендерят "Реакции 1️⃣-9️⃣ переключают навыки." По D-06 реакции убраны полностью. build_skills_text нужно обновить чтобы убрать эту строку и заменить инструкцией !skill on/off <name>.
How to avoid: Обновить build_skills_text + тест test_reactions.py::test_build_skills_text.
Pitfall 7: on_reaction callback остаётся зарегистрированным
What goes wrong: В main() есть client.add_event_callback(bot.on_reaction, ReactionEvent). Если убрать реакции но оставить этот callback — matrix-nio будет продолжать обрабатывать реакции и вызывать on_reaction. Нужно удалить и callback-регистрацию, и импорт ReactionEvent.
Gaps between Current Implementation and Target
| File | Current State | Target State | Action |
|---|---|---|---|
adapter/matrix/handlers/auth.py |
DM join + hardcoded C1 | Space creation + C1 from next_chat_id | Переписать handle_invite |
adapter/matrix/handlers/chat.py |
room_create без Space | room_create + room_put_state в Space | Обновить make_handle_new_chat |
adapter/matrix/bot.py |
on_reaction + _button_action_to_reaction |
Без реакций, pending-state для !yes/!no | Убрать reaction code; обновить send_outgoing |
adapter/matrix/store.py |
Нет space_id, нет pending_confirm |
space_id в room_meta + user_meta; pending_confirm helpers |
Добавить поля и helpers |
adapter/matrix/reactions.py |
build_skills_text упоминает реакции |
build_skills_text без реакций, с !skill on/off |
Обновить текст |
adapter/matrix/handlers/confirm.py |
Заглушка без state | Читает pending_confirm, даёт реальный ответ | Обновить handlers |
adapter/matrix/handlers/settings.py |
handle_settings — список команд |
handle_settings — read-only дашборд (D-12) |
Обновить до дашборда со статусом |
adapter/matrix/converter.py |
from_reaction используется для skill toggle |
Skill toggle через реакции убирается | from_reaction можно оставить или удалить |
Code Examples
Создание Space + child room (verified API)
# Source: matrix-nio installed version — inspect.signature(AsyncClient.room_create)
from nio.responses import RoomCreateError, RoomPutStateError
async def create_user_space(client, display_name: str, matrix_user_id: str, store):
homeserver = matrix_user_id.split(":")[-1] # "@user:matrix.org" → "matrix.org"
# Step 1: Create Space
space_resp = await client.room_create(
name=f"Lambda — {display_name}",
space=True,
visibility="private",
)
if isinstance(space_resp, RoomCreateError):
return None, None
space_id = space_resp.room_id
# Step 2: Create first chat room
chat_resp = await client.room_create(
name="Чат 1",
visibility="private",
is_direct=False,
)
if isinstance(chat_resp, RoomCreateError):
return space_id, None
chat_room_id = chat_resp.room_id
# Step 3: Link child room into Space (state_key = child's room_id)
await client.room_put_state(
room_id=space_id,
event_type="m.space.child",
content={"via": [homeserver]},
state_key=chat_room_id,
)
# Step 4: Invite user to Space and to chat room
await client.room_invite(space_id, matrix_user_id)
await client.room_invite(chat_room_id, matrix_user_id)
return space_id, chat_room_id
send_outgoing для OutgoingUI (без реакций)
if isinstance(event, OutgoingUI):
lines = [event.text]
if event.buttons:
lines.append("")
for btn in event.buttons:
lines.append(f"• {btn.label}")
lines.append("")
lines.append("Ответьте !yes для подтверждения или !no для отмены.")
body = "\n".join(lines)
await client.room_send(room_id, "m.room.message", {"msgtype": "m.text", "body": body})
Проверка ошибок matrix-nio
from nio.responses import RoomCreateError, RoomPutStateError, RoomInviteError
resp = await client.room_create(...)
if isinstance(resp, RoomCreateError):
logger.error("room_create failed", status_code=resp.status_code)
# resp не имеет room_id — безопасный ранний возврат
return [OutgoingMessage(chat_id=event.chat_id, text="Не удалось создать комнату.")]
room_id = resp.room_id # str, гарантированно присутствует
Validation Architecture
nyquist_validation = true в config.json — раздел обязателен.
Test Framework
| Property | Value |
|---|---|
| Framework | pytest + pytest-asyncio |
| Config file | pytest.ini или pyproject.toml (проверить наличие) |
| Quick run command | pytest tests/adapter/matrix/ -q |
| Full suite command | pytest tests/ -q |
| Current count | 97 passed |
Существующие тесты Matrix, требующие обновления
Эти тесты написаны под DM/reaction-based поведение и сломаются после рефакторинга:
| Test | Текущее поведение | После рефакторинга | Действие |
|---|---|---|---|
test_dispatcher.py::test_invite_event_creates_dm_room_and_sends_welcome |
Проверяет chat_id == "C1" через hardcode, join DM |
Должен проверять Space creation + chat room creation | Переписать |
test_dispatcher.py::test_new_chat_creates_real_matrix_room_when_client_available |
Проверяет room_create без Space |
Должен проверять room_create + room_put_state |
Обновить mock + assertions |
test_reactions.py::test_build_skills_text |
Ожидает "Реакции 1️⃣-9️⃣" в тексте | После удаления реакций эта строка исчезнет | Обновить assertion |
test_reactions.py::test_build_confirmation_text |
Проверяет CONFIRM_REACTION + "подтвердить" |
Если build_confirmation_text обновится под D-07 |
Обновить |
Новые тесты, необходимые для покрытия Space+rooms
| ID | Behavior | Test Type | File | Command |
|---|---|---|---|---|
| MAT-01 | handle_invite создаёт Space + Чат 1, сохраняет space_id в user_meta | unit | tests/adapter/matrix/test_invite_space.py |
pytest tests/adapter/matrix/test_invite_space.py -x |
| MAT-02 | handle_invite идемпотентен: повторный вызов не создаёт второй Space | unit | tests/adapter/matrix/test_invite_space.py |
pytest tests/adapter/matrix/test_invite_space.py -x |
| MAT-03 | handle_invite использует next_chat_id, не хардкод "C1" | unit | tests/adapter/matrix/test_invite_space.py |
pytest tests/adapter/matrix/test_invite_space.py -x |
| MAT-04 | make_handle_new_chat вызывает room_put_state с space_id из user_meta | unit | tests/adapter/matrix/test_chat_space.py |
pytest tests/adapter/matrix/test_chat_space.py -x |
| MAT-05 | make_handle_new_chat без space_id возвращает error message | unit | tests/adapter/matrix/test_chat_space.py |
pytest tests/adapter/matrix/test_chat_space.py -x |
| MAT-06 | send_outgoing для OutgoingUI рендерит текст + "!yes / !no", без реакций | unit | tests/adapter/matrix/test_send_outgoing.py |
pytest tests/adapter/matrix/test_send_outgoing.py -x |
| MAT-07 | send_outgoing для OutgoingUI НЕ отправляет m.reaction event | unit | tests/adapter/matrix/test_send_outgoing.py |
pytest tests/adapter/matrix/test_send_outgoing.py -x |
| MAT-08 | get/set/clear_pending_confirm roundtrip в store | unit | tests/adapter/matrix/test_store.py (extend) |
pytest tests/adapter/matrix/test_store.py -x |
| MAT-09 | handle_confirm читает pending_confirm и возвращает описание действия | unit | tests/adapter/matrix/test_confirm.py |
pytest tests/adapter/matrix/test_confirm.py -x |
| MAT-10 | handle_archive вызывает room_put_state с пустым content | unit | tests/adapter/matrix/test_chat_space.py |
pytest tests/adapter/matrix/test_chat_space.py -x |
| MAT-11 | !settings возвращает дашборд со статусом (не список команд) | unit | tests/adapter/matrix/test_dispatcher.py (extend) |
pytest tests/adapter/matrix/test_dispatcher.py -x |
| MAT-12 | RoomCreateError обрабатывается корректно (нет crash, есть user message) | unit | tests/adapter/matrix/test_chat_space.py |
pytest tests/adapter/matrix/test_chat_space.py -x |
Wave 0 Gaps (новые файлы)
tests/adapter/matrix/test_invite_space.py— покрывает MAT-01, MAT-02, MAT-03tests/adapter/matrix/test_chat_space.py— покрывает MAT-04, MAT-05, MAT-10, MAT-12tests/adapter/matrix/test_send_outgoing.py— покрывает MAT-06, MAT-07tests/adapter/matrix/test_confirm.py— покрывает MAT-09
Sampling Rate
- Per task commit:
pytest tests/adapter/matrix/ -q - Per wave merge:
pytest tests/ -q - Phase gate: All 97+ tests green (целевой диапазон 106–110 после добавления новых)
Численный ориентир для "96+ зелёных"
- Сейчас: 97 тестов, все зелёные
- После рефакторинга без добавления тестов: 4 теста сломаются (3 dispatcher + 1 reactions) → ~93 зелёных
- После обновления сломанных: 97 зелёных
- После добавления 12 новых: ~109 зелёных
- Итого: требование "96+" выполнено с запасом
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| matrix-nio | All Matrix API calls | ✓ | установлена, space=True присутствует | — |
| pytest + pytest-asyncio | Test suite | ✓ | работает (97 passed) | — |
| SQLite | SQLiteStore | ✓ | встроен в Python | — |
| Matrix homeserver | Manual QA только | не проверялось | — | Без homeserver — только unit тесты |
Missing dependencies with no fallback: Нет (homeserver нужен только для ручного QA, не для автотестов).
Project Constraints (from CLAUDE.md)
| Directive | Impact on Phase |
|---|---|
core/protocol.py — типы не менять |
IncomingCommand, OutgoingUI, UIButton используем as-is |
Все вызовы платформы через platform/interface.py |
MockPlatformClient остаётся, SDK не трогать |
| Хотфиксы < 20 строк → Claude Code напрямую | Небольшие правки реакций-в-текст могут идти напрямую |
| Реализацию делает Codex | Три задачи — три параллельных Codex запуска |
| Blueprint перед реализацией | Плану нужны blueprint-документы для каждой задачи |
| Порядок зависимостей: core/ → platform/ → adapters/ | Все изменения только в adapter/matrix/, core/ не трогаем |
Open Questions
-
Стоит ли полностью убирать
from_reactionиreactions.py?- D-06 говорит "убрать реакции полностью"
reactions.pyсодержитbuild_confirmation_textиbuild_skills_text— они нужны после рефакторинга- Рекомендация: оставить
reactions.py, удалитьCONFIRM_REACTION/CANCEL_REACTION/add_reaction/remove_reaction, переименовать вformatting.py— но это необязательно для Phase 1.
-
Нужен ли
m.space.parentevent в дочерних комнатах?- Matrix spec позволяет устанавливать
m.space.parentв дочерней комнате, чтобы Element показывал ссылку "назад к Space" - Не является обязательным —
m.space.childв Space достаточно для включения комнаты в Space - Рекомендация: не добавлять в Phase 1, отложить если понадобится.
- Matrix spec позволяет устанавливать
-
viaвm.space.child— один сервер или несколько?- Для single-homeserver деплоя:
["homeserver_domain"]достаточно - Для федерации: нужны несколько серверов
- Рекомендация: парсить из
matrix_user_id.split(":")[-1]— достаточно для текущего использования.
- Для single-homeserver деплоя:
Sources
Primary (HIGH confidence)
- matrix-nio installed package —
AsyncClient.room_create,room_put_state,room_invite,join— сигнатуры и docstrings проверены черезinspect.signatureиhelp() nio.responses.RoomCreateResponse,RoomCreateError,RoomPutStateResponse,RoomPutStateError— поля проверены черезinspect.getsource- Весь codebase прочитан напрямую
Secondary (MEDIUM confidence)
- Matrix Spec v1.x —
m.space.childevent format (content{"via": [...]}, state_key = child room_id) — стандартное поведение, описано в Matrix spec
Metadata
Confidence breakdown:
- matrix-nio API: HIGH — проверено против installed package через Python introspection
- Space creation pattern: HIGH —
space=Trueпараметр подтверждён в room_create signature m.space.childcontent format: MEDIUM — стандарт Matrix spec, не проверен против конкретного homeserver- Archive via empty content: MEDIUM — Matrix spec behaviour, может зависеть от homeserver version
- Тест-план: HIGH — основан на прямом анализе существующих тестов
Research date: 2026-04-02 Valid until: 2026-05-02 (matrix-nio обновляется редко, Space API стабилен с Matrix v1.2)