Files
Warp/docs/spaces.md
T
tajniak81andClaude Opus 5.5 487e0f410b
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Phase 10: hardening and ops
Implements PLAN.md Phase 10.

Simplified Sliding Sync (MSC4186) is served at the unstable
org.matrix.simplified_msc3575 endpoint and advertised in /versions. Lists
are sorted by activity and filtered by DM, invite, encryption, room type,
space and tag; room subscriptions, required_state with wildcards, $ME and
$LAZY, and the to_device, e2ee, account_data, receipts and typing
extensions are supported. Per-connection state is kept in memory and an
unknown pos answers M_UNKNOWN_POS.

Threads and relations: m.relates_to is indexed on roomserver_events
(migration 0012), /relations (with recursion) and /threads are served, and
every event a timeline read returns carries its m.thread, m.replace and
m.reference summaries in unsigned.m.relations. Notifications carry a
thread ID (migration 0013): threaded receipts clear only their thread, and
sync reports unread_thread_notifications when the filter asks.

Spaces: /hierarchy walks a space breadth first, asking other servers about
rooms this one is not in through the new federation hierarchy endpoint;
room_summary describes one room. /search runs on FTS5 (sqlite, pocketbase)
or tsvector (postgres), bounded to what each user may see, with context,
profiles, state, grouping and paging.

The PocketBase driver is real: the sqlite schema and store on an embedded
PocketBase app's database, with media in its filesystem, local or S3
(database.pocketbase.s3). It runs the full storage test suite.

Ops: OpenTelemetry tracing over OTLP/HTTP (tracing.*), with server spans
per route, component spans, and trace propagation on federation, push and
application service calls. New metrics for persisted events, waiting
syncs, sliding sync connections and the database pool, with a Grafana
dashboard. deploy/ adds a Compose setup (PostgreSQL, Prometheus, Grafana,
Jaeger) and a Helm chart; cmd/warp-loadtest loads a server and reports
request and delivery latencies.

The load tester found a long-standing bug, fixed here: on a server where
no account data had been written, every incremental /sync re-sent the push
rules and returned at once, and an all-zero token was taken for an initial
sync, so long polls became busy loops.

The PostgreSQL migrations and search dialect compile but were not run
against a live PostgreSQL in this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:21:31 +02:00

4.0 KiB

Spaces

A space is a room whose create event says "type": "m.space", and whose m.space.child state events name the rooms in it. As with relations, the authorisation rules know nothing of it: a child event is a pointer, and the room it points at need not know. What the server provides is the walk.

The code is in internal/roomserver/hierarchy.go.

GET /_matrix/client/v1/rooms/{roomID}/hierarchy

The space's tree, breadth first, a page at a time. Each room is described the way the public room directory describes it — name, topic, avatar, alias, member count, join rule, room type, room version and encryption — plus its children_state: the m.space.child events, each with its origin_server_ts.

Parameter
max_depth How far below the root to go; 0 is the root alone. Capped at 50.
suggested_only Follow only children marked "suggested": true.
limit Rooms per page: 50 by default, 100 at most.
from The previous page's next_batch.

Children are visited in the spec's order: those with a valid order (printable ASCII, at most 50 characters) by that string, then by when the child event was sent, then by room ID. A child event whose via is missing or empty is how a space removes a child — a state event cannot be deleted, only emptied — and is skipped. Only a space's children are walked into: an ordinary room that happens to hold an m.space.child event is not a space.

Paging walks from the root again and skips what earlier pages returned. That keeps the server free of per-walk state, and a walk is capped at 2000 rooms whatever the page size, which bounds the cost of the last page of a very large space.

Who sees what

A room is shown to a user who:

  • is joined to it or invited to it; or
  • may know about it anyway: its join rule is public, knock or knock_restricted, or its history is world-readable; or
  • is joined to one of the rooms a restricted room lets in.

Everything else is left out, and so are its children. The root itself is the exception: a space the user may not see is 403.

Rooms this server is not in

A child this server is not in is asked about over federation, of the servers its via names, with GET /_matrix/federation/v1/hierarchy. The answer is remembered for the rest of the walk. The other server has already judged the room for this server as a whole; Warp then checks a restricted room's allow list against its own user. A child that no server will describe is left out. With federation off, rooms this server is not in are simply not shown.

GET /_matrix/federation/v1/hierarchy/{roomID}

Another server walking one of Warp's spaces: the room and the children Warp is in, each judged for the asking server. A room is shown to a server when anybody could be shown it (see above), when it is restricted — with allowed_room_ids, so the asking server can check its own users — or when a user of that server is joined or invited. Children it may not see are listed in inaccessible_children. A root it may not see is 404, which is also the answer for a room Warp is not in.

GET /_matrix/client/v1/room_summary/{roomIDOrAlias}

One room's face, for a link preview or an invite: the same fields as a hierarchy room without its children, plus the user's membership (leave for a user who was never in it). An alias is resolved first, and the servers it names are added to ?via=. The same visibility rules apply; a room the user may not see is 403.

The endpoint is in the spec from v1.15. Warp still advertises v1.14, because v1.15 also brought requirements Warp does not meet yet.

What is deliberately missing

Thing Why
Unauthenticated room_summary The spec allows it for guests; Warp asks for a token.
Caching other servers' hierarchy answers between walks Each walk asks again; a space with many remote children costs a federation request per child per page.
m.space.parent checks Parent events are state like any other, and are not used to validate children.