Add Spaces to the spec (#3610)

* First iteration of specifying Spaces

MSCs:
* https://github.com/matrix-org/matrix-doc/pull/3288
* https://github.com/matrix-org/matrix-doc/pull/2946
* https://github.com/matrix-org/matrix-doc/pull/1772

Note that this makes modifications to the underlying MSCs as well. These are intended to be minor edits to aid clarity/accuracy of the MSCs, as per the proposal process. Functionally, clients and servers might need to change their behaviour slightly as is expected of implementing this stuff early. Synapse has these changes (alongside backwards compatibility) here: https://github.com/matrix-org/synapse/pull/11667

* add changelogs

* Accuracy per review

* Apply suggestions from code review

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* fully prefix new endpoints

* Fully prefix endpoint in 3616 too

* Fix ordering example

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
pull/977/head
Travis Ralston 3 years ago committed by GitHub
parent 21882b6006
commit 9af83dfd41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1 @@
Add Spaces and room types as per [MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772) and [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946).

@ -0,0 +1 @@
Add the Space Hierarchy API (`GET /_matrix/client/v1/rooms/{roomId}/hierarchy`) as per [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946).

@ -1 +1 @@
Add `/register/m.login.registration_token/validity` as per [MSC3231](https://github.com/matrix-org/matrix-doc/pull/3231). Add `/_matrix/client/v1/register/m.login.registration_token/validity` as per [MSC3231](https://github.com/matrix-org/matrix-doc/pull/3231).

@ -0,0 +1 @@
Add the `room_type` to stored invites as per [MSC3288](https://github.com/matrix-org/matrix-doc/pull/3288).

@ -0,0 +1 @@
Add the Space Hierarchy API (`GET /_matrix/federation/v1/hierarchy/{roomId}`) as per [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946).

@ -1854,6 +1854,27 @@ the topic to be removed from the room.
## Rooms ## Rooms
### Types
{{% added-in v="1.2" %}}
Optionally, rooms can have types to denote their intended function. A room
without a type does not necessarily mean it has a specific default function,
though commonly these rooms will be for conversational purposes.
Room types are best applied when a client might need to differentiate between
two different rooms, such as conversation-holding and data-holding. If a room
has a type, it is specified in the `type` key of an [`m.room.create`](#mroomcreate)
event. To specify a room's type, provide it as part of `creation_content` on
the create room request.
In this specification the following room types are specified:
* [`m.space`](#spaces)
Unspecified room types are permitted through the use of
[Namespaced Identifiers](/appendices/#common-namespaced-identifier-grammar).
### Creation ### Creation
The homeserver will create an `m.room.create` event when a room is The homeserver will create an `m.room.create` event when a room is
@ -2190,6 +2211,7 @@ that profile.
| [Server ACLs](#server-access-control-lists-acls-for-rooms) | Optional | Optional | Optional | Optional | Optional | | [Server ACLs](#server-access-control-lists-acls-for-rooms) | Optional | Optional | Optional | Optional | Optional |
| [Server Notices](#server-notices) | Optional | Optional | Optional | Optional | Optional | | [Server Notices](#server-notices) | Optional | Optional | Optional | Optional | Optional |
| [Moderation policies](#moderation-policy-lists) | Optional | Optional | Optional | Optional | Optional | | [Moderation policies](#moderation-policy-lists) | Optional | Optional | Optional | Optional | Optional |
| [Spaces](#spaces) | Optional | Optional | Optional | Optional | Optional |
*Please see each module for more details on what clients need to *Please see each module for more details on what clients need to
implement.* implement.*

@ -0,0 +1,254 @@
---
type: module
weight: 340
---
### Spaces
{{% added-in v="1.2" %}}
Often used to group rooms of similar subject matter (such as a public "Official
matrix.org rooms" space or personal "Work stuff" space), spaces are a way to
organise rooms while being represented as rooms themselves.
A space is defined by the [`m.space` room type](#types), making it known as a
"space-room". The space's name, topic, avatar, aliases, etc are all defined through
the existing relevant state events within the space-room.
Sending normal [`m.room.message`](#mroommessage) events within the space-room is
discouraged - clients are not generally expected to have a way to render the timeline
of the room. As such, space-rooms should be created with [`m.room.power_levels`](#mroompower_levels)
which prohibit normal events by setting `events_default` to a suitably high number.
In the default power level structure, this would be `100`. Clients might wish to
go a step further and explicitly ignore notification counts on space-rooms.
Membership of a space is defined and controlled by the existing mechanisms which
govern a room: [`m.room.member`](#mroommember), [`m.room.history_visibility`](#mroomhistory_visibility),
and [`m.room.join_rules`](#mroomjoin_rules). Public spaces are encouraged to have
a similar setup to public rooms: `world_readable` history visibility, published
canonical alias, and suitably public `join_rule`. Invites, including third-party
invites, still work just as they do in normal rooms as well.
All other aspects of regular rooms are additionally carried over, such as the
ability to set arbitrary state events, hold room account data, etc. Spaces are
just rooms with extra functionality on top.
#### Managing rooms/spaces included in a space
Spaces form a hierarchy of rooms which clients can use to structure their room
list into a tree-like view. The parent/child relationship can be defined in two
ways: with [`m.space.child`](#mspacechild) state events in the space-room, or with
[`m.space.parent`](#mspaceparent) state events in the child room.
In most cases, both the child and parent relationship should be defined to aid
discovery of the space and its rooms. When only a `m.space.child` is used, the space
is effectively a curated list of rooms which the rooms themselves might not be aware
of. When only a `m.space.parent` is used, the rooms are "secretly" added to spaces
with the effect of not being advertised directly by the space.
{{% boxes/warning %}}
Considering spaces are rooms themselves, it is possible to nest spaces within spaces,
and it is possible to create a loop. Though the creation of loops is explicitly disallowed,
implementations might still encounter them and must be careful not to loop infinitely when
this happens.
Clients and servers should additionally be aware of excessively long trees which may
cause performance issues.
{{% /boxes/warning %}}
##### `m.space.child` relationship
When using this approach, the state events get sent into the space-room which is the
parent to the room. The `state_key` for the event is the child room's ID.
For example, to achieve the following:
```
#space:example.org
#general:example.org (!abcdefg:example.org)
!private:example.org
```
the state of `#space:example.org` would consist of:
*Unimportant fields trimmed for brevity.*
```json
{
"type": "m.space.child",
"state_key": "!abcdefg:example.org",
"content": {
"via": ["example.org"]
}
}
```
```json
{
"type": "m.space.child",
"state_key": "!private:example.org",
"content": {
"via": ["example.org"]
}
}
```
No state events in the child rooms themselves would be required (though they
can also be present). This allows for users
to define personal/private spaces to organise their own rooms without needing explicit
permission from the room moderators/admins.
Child rooms can be removed from a space by omitting the `via` key of `content` on the
relevant state event, such as through redaction or otherwise clearing the `content`.
{{% event event="m.space.child" %}}
###### Ordering
When the client is displaying the children of a space, the children should be ordered
using the algorithm below. In some cases, like a traditional left side room list, the
client may override the ordering to provide better user experience. A theoretical
space summary view would however show the children ordered.
Taking the set of space children, first order the children with a valid `order` key
lexicographically by Unicode code-points such that `\x20` (space) is sorted before
`\x7E` (`~`). Then, take the remaining children and order them by the `origin_server_ts`
of their `m.space.child` event in ascending numeric order, placing them after the
children with a valid `order` key in the resulting set.
In cases where the `order` values are the same, the children are ordered by their
timestamps. If the timestamps are the same, the children are ordered lexicographically
by their room IDs (state keys) in ascending order.
Noting the careful use of ASCII spaces here, the following demonstrates a set of space
children being ordered appropriately:
*Unimportant fields trimmed for brevity.*
```json
[
{
"type": "m.space.child",
"state_key": "!b:example.org",
"origin_server_ts": 1640341000000,
"content": {
"order": " ",
"via": ["example.org"]
}
},
{
"type": "m.space.child",
"state_key": "!a:example.org",
"origin_server_ts": 1640141000000,
"content": {
"order": "aaaa",
"via": ["example.org"]
}
},
{
"type": "m.space.child",
"state_key": "!c:example.org",
"origin_server_ts": 1640841000000,
"content": {
"order": "first",
"via": ["example.org"]
}
},
{
"type": "m.space.child",
"state_key": "!e:example.org",
"origin_server_ts": 1640641000000,
"content": {
"via": ["example.org"]
}
},
{
"type": "m.space.child",
"state_key": "!d:example.org",
"origin_server_ts": 1640741000000,
"content": {
"via": ["example.org"]
}
}
]
```
1. `!b:example.org` is first because `\x20` is before `aaaa` lexically.
2. `!a:example.org` is next because `aaaa` is before `first` lexically.
3. `!c:example.org` is next because `first` is the last `order` value.
4. `!e:example.org` is next because the event timestamp is smallest.
5. `!d:example.org` is last because the event timestamp is largest.
##### `m.space.parent` relationships
Rooms can additionally claim to be part of a space by populating their own state
with a parent event. Similar to child events within spaces, the parent event's
`state_key` is the room ID of the parent space, and they have a similar `via` list
within their `content` to denote both whether or not the link is valid and which
servers might be possible to join through.
To avoid situations where a room falsely claims it is part of a given space,
`m.space.parent` events should be ignored unless one of the following is true:
* A corresponding `m.space.child` event can be found in the supposed parent space.
* The sender of the `m.space.parent` event has sufficient power level in the
supposed parent space to send `m.space.child` state events (there doesn't need
to be a matching child event).
{{% boxes/note %}}
Clients might need to peek into a parent space to inspect the room state if they
aren't already joined. If the client is unable to peek the state, the link should
be assumed to be invalid.
{{% /boxes/note %}}
{{% boxes/note %}}
A consequence of the second condition is that a room admin being demoted in the
parent space, leaving the parent space, or otherwise being removed from the parent
space can mean that a previously valid `m.space.parent` event becomes invalid.
{{% /boxes/note %}}
`m.space.parent` events can additionally include a `canonical` boolean key in their
`content` to denote that the parent space is the main/primary space for the room.
This can be used to, for example, have the client find other rooms by peeking into
that space and suggesting them to the user. Only one canonical parent should exist,
though this is not enforced. To tiebreak, use the lowest room ID sorted lexicographically
by Unicode code-points.
{{% event event="m.space.parent" %}}
#### Discovering rooms within spaces
Often the client will want to assist the user in exploring what rooms/spaces are part
of a space. This can be done with crawling [`m.space.child`](#mspacechild) state events
in the client and peeking into the rooms to get information like the room name, though
this is impractical for most cases.
Instead, a hierarchy API is provided to walk the space tree and discover the rooms with
their aesthetic details.
The [`GET /hierarchy`](#get_matrixclientv1roomsroomidhierarchy) API works in a depth-first
manner: when it encounters another space as a child it recurses into that space before
returning non-space children.
{{% boxes/warning %}}
Though prohibited, it is still possible for loops to occur. Servers should gracefully
break loops.
Additionally, a given child room might appear multiple times in the response as a
grandchild (for example).
{{% /boxes/warning %}}
{{% http-api spec="client-server" api="space_hierarchy" %}}
##### Server behaviour
In the case where the server does not have access to the state of a child room, it can
request the information over federation with the
[`GET /hierarchy`](/server-server-api/#get_matrixfederationv1hierarchyroomid) API. The
response to this endpoint should be cached for a period of time. The response might
additionally contain information about rooms the requesting user is already a member
of, or that the server is aware of - the local data should be used instead of the remote
server's data.
Note that the response to the client endpoint is contextual based on the user. Servers are
encouraged to cache the data for a period of time, though permission checks may need to
be performed to ensure the response is accurate for that user.

@ -902,6 +902,13 @@ the server the room directory should be retrieved for.
{{% http-api spec="server-server" api="public_rooms" %}} {{% http-api spec="server-server" api="public_rooms" %}}
## Spaces
To complement the [Client-Server API's Spaces module](/client-server-api/#spaces),
homeservers need a way to query information about spaces from other servers.
{{% http-api spec="server-server" api="space_hierarchy" %}}
## Typing Notifications ## Typing Notifications
When a server's users send typing notifications, those notifications When a server's users send typing notifications, those notifications

@ -0,0 +1,70 @@
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
type: object
title: "PublicRoomsChunk"
properties:
aliases:
type: array
description: Aliases of the room. May be empty.
items:
type: string
example: ["#general:example.org"]
canonical_alias:
type: string
description: The canonical alias of the room, if any.
example: "#general:example.org"
name:
type: string
description: The name of the room, if any.
example: "General Chat"
num_joined_members:
type: integer
description: The number of members joined to the room.
example: 42
room_id:
type: string
description: The ID of the room.
example: "!abcdefg:example.org"
topic:
type: string
description: The topic of the room, if any.
example: "All things general"
world_readable:
type: boolean
description: Whether the room may be viewed by guest users without joining.
example: false
guest_can_join:
type: boolean
description: |-
Whether guest users may join the room and participate in it.
If they can, they will be subject to ordinary power level
rules like any other user.
example: true
avatar_url:
type: string
format: uri
description: The URL for the room's avatar, if one is set.
example: "mxc://example.org/abcdef"
join_rule:
type: string
description: |-
The room's join rule. When not present, the room is assumed to
be `public`.
example: "public"
required:
- room_id
- num_joined_members
- world_readable
- guest_can_join

@ -22,61 +22,19 @@ properties:
description: |- description: |-
A paginated chunk of public rooms. A paginated chunk of public rooms.
items: items:
type: object allOf:
title: "PublicRoomsChunk" - $ref: "public_rooms_chunk.yaml"
required: - type: object
- room_id properties:
- num_joined_members # Override description of join_rule
- world_readable join_rule:
- guest_can_join type: string
properties: description: |-
aliases: The room's join rule. When not present, the room is assumed to
type: array be `public`. Note that rooms with `invite` join rules are not
description: |- expected here, but rooms with `knock` rules are given their
Aliases of the room. May be empty. near-public nature.
items: example: "public"
type: string
canonical_alias:
type: string
description: |-
The canonical alias of the room, if any.
name:
type: string
description: |-
The name of the room, if any.
num_joined_members:
type: integer
description: |-
The number of members joined to the room.
room_id:
type: string
description: |-
The ID of the room.
topic:
type: string
description: |-
The topic of the room, if any.
world_readable:
type: boolean
description: |-
Whether the room may be viewed by guest users without joining.
guest_can_join:
type: boolean
description: |-
Whether guest users may join the room and participate in it.
If they can, they will be subject to ordinary power level
rules like any other user.
avatar_url:
type: string
format: uri
description: The URL for the room's avatar, if one is set.
join_rule:
type: string
description: |-
The room's join rule. When not present, the room is assumed to
be `public`. Note that rooms with `invite` join rules are not
expected here, but rooms with `knock` rules are given their
near-public nature.
next_batch: next_batch:
type: string type: string
description: |- description: |-

@ -0,0 +1,194 @@
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
swagger: '2.0'
info:
title: "Matrix Client-Server Space Hierarchy API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/v1
consumes:
- application/json
produces:
- application/json
securityDefinitions:
$ref: definitions/security.yaml
paths:
"/rooms/{roomId}/hierarchy":
get:
x-addedInMatrixVersion: "1.2"
summary: Retrieve a portion of a space tree.
description: |-
Paginates over the space tree in a depth-first manner to locate child rooms of a given space.
Where a child room is unknown to the local server, federation is used to fill in the details.
The servers listed in the `via` array should be contacted to attempt to fill in missing rooms.
Only [`m.space.child`](#mspacechild) state events of the room are considered. Invalid child
rooms and parent events are not covered by this endpoint.
operationId: getSpaceHierarchy
security:
- accessToken: []
parameters:
- in: path
type: string
name: roomId
description: The room ID of the space to get a hierarchy for.
required: true
x-example: "!space:example.org"
- in: query
type: boolean
name: suggested_only
description: |-
Optional (default `false`) flag to indicate whether or not the server should only consider
suggested rooms. Suggested rooms are annotated in their [`m.space.child`](#mspacechild) event
contents.
x-example: true
- in: query
type: number
name: limit
description: |-
Optional limit for the maximum number of rooms to include per response. Must be an integer
greater than zero.
Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.
x-example: 20
- in: query
type: number
name: max_depth
description: |-
Optional limit for how far to go into the space. Must be a non-negative integer.
When reached, no further child rooms will be returned.
Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.
x-example: 5
- in: query
type: string
name: from
description: |-
A pagination token from a previous result. If specified, `max_depth` and `suggested_only` cannot
be changed from the first request.
x-example: "next_batch_token"
responses:
200:
description: |-
A portion of the space tree, starting at the provided room ID.
examples:
application/json: {
"rooms": [
{
"room_id": "!space:example.org",
"avatar_url": "mxc://example.org/abcdef",
"guest_can_join": false,
"name": "The First Space",
"topic": "No other spaces were created first, ever",
"world_readable": true,
"join_rule": "public",
"room_type": "m.space",
"num_joined_members": 42,
"aliases": ["#general:example.org"],
"canonical_alias": "#general:example.org",
"children_state": [
{
"type": "m.space.child",
"state_key": "!a:example.org",
"content": {
"via": ["example.org"]
},
"sender": "@alice:example.org",
"origin_server_ts": 1629413349153
}
]
}
],
"next_batch": "next_batch_token"
}
schema:
type: object
properties:
rooms:
type: array
description: |-
The rooms for the current page, with the current filters.
items:
allOf:
- $ref: "definitions/public_rooms_chunk.yaml"
- type: object
properties:
room_type:
type: string
description: |-
The `type` of room (from [`m.room.create`](/client-server-api/#mroomcreate)), if any.
children_state:
type: array
description: |-
The [`m.space.child`](#mspacechild) events of the space-room, represented
as [Stripped State Events](#stripped-state) with an added `origin_server_ts` key.
If the room is not a space-room, this should be empty.
items:
allOf:
- $ref: "../../event-schemas/schema/core-event-schema/stripped_state.yaml"
- type: object
properties:
origin_server_ts:
type: number
format: int64
description: The `origin_server_ts` for the event.
required: [origin_server_ts]
required: [room_type, children_state]
next_batch:
type: string
description: |-
A token to supply to `from` to keep paginating the responses. Not present when there are
no further results.
required: [rooms]
403:
description: |-
The user cannot view or peek on the room. A meaningful `errcode`
and description error text will be returned. Example reasons for rejection are:
- The room is not set up for peeking.
- The user has been banned from the room.
- The room does not exist.
examples:
application/json: {
"errcode": "M_FORBIDDEN",
"error": "You are not allowed to view this room."
}
schema:
"$ref": "definitions/errors/error.yaml"
400:
description: |-
The request was invalid in some way. A meaningful `errcode`
and description error text will be returned. Example reasons for rejection are:
- The `from` token is unknown to the server.
- `suggested_only` or `max_depth` changed during pagination.
examples:
application/json: {
"errcode": "M_INVALID_PARAM",
"error": "suggested_only and max_depth cannot change on paginated requests"
}
schema:
"$ref": "definitions/errors/error.yaml"
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/errors/rate_limited.yaml"
tags:
- Spaces

@ -47,7 +47,9 @@ paths:
The service records persistently all of the above information. The service records persistently all of the above information.
It also generates an email containing all of this data, sent to the It also generates an email containing all of this data, sent to the
`address` parameter, notifying them of the invitation. `address` parameter, notifying them of the invitation. The email should
reference the `inviter_name`, `room_name`, `room_avatar`, and `room_type`
(if present) from the request here.
Also, the generated ephemeral public key will be listed as valid on Also, the generated ephemeral public key will be listed as valid on
requests to `/_matrix/identity/v2/pubkey/ephemeral/isvalid`. requests to `/_matrix/identity/v2/pubkey/ephemeral/isvalid`.
@ -115,6 +117,12 @@ paths:
type: string type: string
description: The Content URI for the avatar of the user ID initiating the invite. description: The Content URI for the avatar of the user ID initiating the invite.
example: "mxc://example.org/an0th3rM3dia" example: "mxc://example.org/an0th3rM3dia"
room_type:
type: string
description: |-
The `type` from the `m.room.create` event's `content`. If the create event doesn't
have a specified `type`, this field is not included.
example: "m.space"
required: ["medium", "address", "room_id", "sender"] required: ["medium", "address", "room_id", "sender"]
responses: responses:
200: 200:

@ -143,63 +143,20 @@ paths:
chunk: chunk:
title: "PublicRoomsChunks" title: "PublicRoomsChunks"
type: array type: array
description: |- description: A paginated chunk of public rooms.
A paginated chunk of public rooms.
items: items:
type: object allOf:
title: "PublicRoomsChunk" - $ref: "../client-server/definitions/public_rooms_chunk.yaml"
required: - type: object
- room_id properties:
- num_joined_members # Override description of join_rule
- world_readable join_rule:
- guest_can_join type: string
properties: description: |-
aliases: The room's join rule. When not present, the room is assumed to
type: array be `public`. Note that rooms with `invite` join rules are not
description: |- expected here, but rooms with `knock` rules are given their
Aliases of the room. May be empty. near-public nature.
items:
type: string
canonical_alias:
type: string
description: |-
The canonical alias of the room, if any.
name:
type: string
description: |-
The name of the room, if any.
num_joined_members:
type: integer
description: |-
The number of members joined to the room.
room_id:
type: string
description: |-
The ID of the room.
topic:
type: string
description: |-
The topic of the room, if any.
world_readable:
type: boolean
description: |-
Whether the room may be viewed by guest users without joining.
guest_can_join:
type: boolean
description: |-
Whether guest users may join the room and participate in it.
If they can, they will be subject to ordinary power level
rules like any other user.
avatar_url:
type: string
description: The URL for the room's avatar, if one is set.
join_rule:
type: string
description: |-
The room's join rule. When not present, the room is assumed to
be `public`. Note that rooms with `invite` join rules are not
expected here, but rooms with `knock` rules are given their
near-public nature.
next_batch: next_batch:
type: string type: string
description: |- description: |-

@ -0,0 +1,207 @@
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
swagger: '2.0'
info:
title: "Matrix Federation Space Hierarchy API"
version: "1.0.0"
host: localhost:8448
schemes:
- https
basePath: /_matrix/federation/v1
consumes:
- application/json
produces:
- application/json
securityDefinitions:
$ref: definitions/security.yaml
paths:
"/hierarchy/{roomId}":
get:
x-addedInMatrixVersion: "1.2"
summary: Retrieve a portion of a space tree.
description: |-
Federation version of the Client-Server [`GET /hierarchy`](/client-server-api/#get_matrixclientv1roomsroomidhierarchy)
endpoint. Unlike the Client-Server API version, this endpoint does not paginate. Instead, all
the space-room's children the requesting server could feasibly peek/join are returned. The
requesting server is responsible for filtering the results further down for the user's request.
Only [`m.space.child`](#mspacechild) state events of the room are considered. Invalid child
rooms and parent events are not covered by this endpoint.
Responses to this endpoint should be cached for a period of time.
operationId: getSpaceHierarchy
security:
- signedRequest: []
parameters:
- in: path
type: string
name: roomId
description: The room ID of the space to get a hierarchy for.
required: true
x-example: "!space:example.org"
- in: query
type: boolean
name: suggested_only
description: |-
Optional (default `false`) flag to indicate whether or not the server should only consider
suggested rooms. Suggested rooms are annotated in their [`m.space.child`](#mspacechild) event
contents.
x-example: true
responses:
200:
description: |-
The space room and its children.
examples:
application/json: {
"room": {
"room_id": "!space:example.org",
"avatar_url": "mxc://example.org/abcdef",
"guest_can_join": false,
"name": "The First Space",
"topic": "No other spaces were created first, ever",
"world_readable": true,
"join_rule": "public",
"room_type": "m.space",
"num_joined_members": 42,
"aliases": ["#general:example.org"],
"canonical_alias": "#general:example.org",
"allowed_room_ids": [],
"children_state": [
{
"type": "m.space.child",
"state_key": "!a:example.org",
"content": {
"via": ["remote.example.org"]
},
"sender": "@alice:example.org",
"origin_server_ts": 1629413349153
}
]
},
"inaccessible_children": [
"!secret:example.org"
],
"children": [
{
"room_id": "!second_room:example.org",
"avatar_url": "mxc://example.org/abcdef2",
"guest_can_join": false,
"name": "The ~~First~~ Second Space",
"topic": "Hello world",
"world_readable": true,
"join_rule": "restricted",
"room_type": "m.space",
"num_joined_members": 42,
"aliases": ["#general:example.org"],
"canonical_alias": "#general:example.org",
"allowed_room_ids": [
"!upstream:example.org"
],
"children_state": [
{
"type": "m.space.child",
"state_key": "!b:example.org",
"content": {
"via": ["remote.example.org"]
},
"sender": "@alice:example.org",
"origin_server_ts": 1629422222222
}
]
}
]
}
schema:
type: object
properties:
room:
description: A summary of the room requested.
allOf:
- $ref: "../client-server/definitions/public_rooms_chunk.yaml"
- type: object
properties:
room_type:
type: string
description: |-
The `type` of room (from [`m.room.create`](/client-server-api/#mroomcreate)), if any.
allowed_room_ids:
type: array
items:
type: string
description: |-
If the room is a [restricted room](#restricted-rooms), these are the room IDs which
are specified by the join rules. Empty or omitted otherwise.
children_state:
type: array
description: |-
The [`m.space.child`](/client-server-api/#mspacechild) events of the space-room, represented
as [Stripped State Events](/client-server-api/#stripped-state) with an added
`origin_server_ts` key.
If the room is not a space-room, this should be empty.
items:
allOf:
- $ref: "../../event-schemas/schema/core-event-schema/stripped_state.yaml"
- type: object
properties:
origin_server_ts:
type: number
format: int64
description: The `origin_server_ts` for the event.
required: [origin_server_ts]
required: [room_type, allowed_room_ids, children_state]
children:
description: |-
A summary of the space's children. Rooms which the requesting server cannot peek/join will
be excluded.
allOf:
- $ref: "../client-server/definitions/public_rooms_chunk.yaml"
- type: object
properties:
room_type:
type: string
description: |-
The `type` of room (from [`m.room.create`](/client-server-api/#mroomcreate)), if any.
allowed_room_ids:
type: array
items:
type: string
description: |-
If the room is a [restricted room](#restricted-rooms), these are the room IDs which
are specified by the join rules. Empty or omitted otherwise.
required: [room_type, allowed_room_ids, children_state]
inaccessible_children:
type: array
items:
type: string
description: |-
The list of room IDs the requesting server doesn't have a viable way to peek/join. Rooms which
the responding server cannot provide details on will be outright excluded from the response instead.
Assuming both the requesting and responding server are well behaved, the requesting server should
consider these room IDs as not accessible from anywhere. They should not be re-requested.
required: [room, children, inaccessible_children]
404:
description: |-
The room is not known to the server or the requesting server is unable to peek/join
it (if it were to attempt this).
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Room does not exist."
}
schema:
"$ref": "../client-server/definitions/errors/error.yaml"
tags:
- Spaces

@ -0,0 +1,10 @@
{
"$ref": "core/state_event.json",
"type": "m.space.child",
"state_key": "!roomid:example.org",
"content": {
"suggested": true,
"via": ["example.org", "other.example.org"],
"order": "lexicographically_compare_me"
}
}

@ -0,0 +1,9 @@
{
"$ref": "core/state_event.json",
"type": "m.space.parent",
"state_key": "!parent_roomid:example.org",
"content": {
"canonical": true,
"via": ["example.org", "other.example.org"]
}
}

@ -14,6 +14,12 @@ properties:
room_version: room_version:
description: The version of the room. Defaults to `"1"` if the key does not exist. description: The version of the room. Defaults to `"1"` if the key does not exist.
type: string type: string
type:
description: |-
Optional [room type](#types) to denote a room's intended function outside of traditional conversation.
Unspecified room types are possible using [Namespaced Identifiers](/appendices/#common-namespaced-identifier-grammar).
type: string
predecessor: predecessor:
description: A reference to the room this room replaces, if the previous room was upgraded. description: A reference to the room this room replaces, if the previous room was upgraded.
type: object type: object

@ -0,0 +1,46 @@
---
allOf:
- $ref: core-event-schema/state_event.yaml
description: Defines the relationship of a child room to a space-room. Has no effect in rooms which are not [spaces](#spaces).
properties:
content:
properties:
via:
type: array
description: |-
A list of servers to try and join through. See also: [Routing](/appendices/#routing).
When not present or invalid, the child room is not considered to be part of the space.
items:
type: string
order:
type: string
maxLength: 50
pattern: '^[\x20-\x7E]+$'
description: |-
Optional string to define ordering among space children. These are lexicographically
compared against other children's `order`, if present.
Must consist of ASCII characters within the range `\x20` (space) and `\x7E` (`~`),
inclusive. Must not exceed 50 characters.
`order` values with the wrong type, or otherwise invalid contents, are to be treated
as though the `order` key was not provided.
See [Ordering](/client-server-api/#ordering-1) for information on how the ordering works.
suggested:
type: boolean
description: |-
Optional (default `false`) flag to denote whether the child is "suggested" or of interest
to members of the space. This is primarily intended as a rendering hint for clients to
display the room differently, such as eagerly rendering them in the room list.
type: object
state_key:
description: The child room ID being described.
type: string
type:
enum:
- m.space.child
type: string
title: Space child room
type: object

@ -0,0 +1,32 @@
---
allOf:
- $ref: core-event-schema/state_event.yaml
description: Defines the relationship of a room to a parent space-room.
properties:
content:
properties:
via:
type: array
description: |-
A list of servers to try and join through. See also: [Routing](/appendices/#routing).
When not present or invalid, the room is not considered to be part of the parent space.
items:
type: string
canonical:
type: boolean
description: |-
Optional (default `false`) flag to denote this parent is the primary parent for the room.
When multiple `canonical` parents are found, the lowest parent when ordering by room ID
lexicographically by Unicode code-points should be used.
type: object
state_key:
description: The parent room ID.
type: string
type:
enum:
- m.space.parent
type: string
title: Room space parent
type: object

@ -122,8 +122,9 @@ relationship can be expressed in one of two ways:
The `order` key is a string which is used to provide a default ordering of The `order` key is a string which is used to provide a default ordering of
siblings in the room list. (Rooms are sorted based on a lexicographic siblings in the room list. (Rooms are sorted based on a lexicographic
ordering of the Unicode codepoints of the characters in `order` values. ordering of the Unicode codepoints of the characters in `order` values.
Rooms with no `order` come last, in ascending numeric order of the Rooms with no `order` come last with no effective `order`. When the `order`
`origin_server_ts` of their `m.room.create` events, or ascending (or lack thereof) is the same, the rooms are sorted in ascending numeric
order of the `origin_server_ts` of their `m.room.create` events, or ascending
lexicographic order of their `room_id`s in case of equal lexicographic order of their `room_id`s in case of equal
`origin_server_ts`. `order`s which are not strings, or do not consist `origin_server_ts`. `order`s which are not strings, or do not consist
solely of ascii characters in the range `\x20` (space) to `\x7E` (`~`), or solely of ascii characters in the range `\x20` (space) to `\x7E` (`~`), or
@ -161,7 +162,7 @@ relationship can be expressed in one of two ways:
To avoid abuse where a room admin falsely claims that a room is part of a To avoid abuse where a room admin falsely claims that a room is part of a
space that it should not be, clients could ignore such `m.space.parent` space that it should not be, clients could ignore such `m.space.parent`
events unless either (a) there is a corresponding `m.space.child` event in events unless either (a) there is a corresponding `m.space.child` event in
the claimed parent, or (b) the sender of the `m.space.child` event has a the claimed parent, or (b) the sender of the `m.space.parent` event has a
sufficient power-level to send such an `m.space.child` event in the sufficient power-level to send such an `m.space.child` event in the
parent. (It is not necessarily required that that user currently be a parent. (It is not necessarily required that that user currently be a
member of the parent room - only the `m.room.power_levels` event is member of the parent room - only the `m.room.power_levels` event is

@ -88,7 +88,7 @@ Query Parameters:
`/publicRooms` (see `/publicRooms` (see
[spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)), [spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)),
with the addition of: with the addition of:
* **`room_type`**: the value of the `m.type` field from the room's * **`room_type`**: the value of the `type` field from the room's
`m.room.create` event, if any. `m.room.create` event, if any.
* **`children_state`**: The stripped state of the `m.space.child` events of * **`children_state`**: The stripped state of the `m.space.child` events of
the room per [MSC3173](https://github.com/matrix-org/matrix-doc/pull/3173). the room per [MSC3173](https://github.com/matrix-org/matrix-doc/pull/3173).
@ -115,6 +115,7 @@ GET /_matrix/client/v1/rooms/%21ol19s%3Ableecker.street/hierarchy?
{ {
"rooms": [ "rooms": [
{ {
// Fields from PublicRoomsChunk
"room_id": "!ol19s:bleecker.street", "room_id": "!ol19s:bleecker.street",
"avatar_url": "mxc://bleecker.street/CHEDDARandBRIE", "avatar_url": "mxc://bleecker.street/CHEDDARandBRIE",
"guest_can_join": false, "guest_can_join": false,
@ -122,7 +123,11 @@ GET /_matrix/client/v1/rooms/%21ol19s%3Ableecker.street/hierarchy?
"num_joined_members": 37, "num_joined_members": 37,
"topic": "Tasty tasty cheese", "topic": "Tasty tasty cheese",
"world_readable": true, "world_readable": true,
"join_rules": "public", "join_rule": "public",
"canonical_alias": "#cheese:bleecker.street",
"aliases": ["#cheese:bleecker.street"],
// Added fields
"room_type": "m.space", "room_type": "m.space",
"children_state": [ "children_state": [
{ {
@ -203,13 +208,13 @@ easily known the room summary might have changed).
Since the server-server API does not know the requesting user, the response should Since the server-server API does not know the requesting user, the response should
divulge information based on if any member of the requesting server could join divulge information based on if any member of the requesting server could join
the room. The requesting server is trusted to properly filter this information the room. The requesting server is trusted to properly filter this information
using the `world_readable`, `join_rules`, and `allowed_room_ids` fields from the using the `world_readable`, `join_rule`, and `allowed_room_ids` fields from the
response. response.
If the target server is not a member of some children rooms (so would have to send If the target server is not a member of some children rooms (so would have to send
another request over federation to inspect them), no attempt is made to recurse another request over federation to inspect them), no attempt is made to recurse
into them. They are simply omitted from the `children` key of the response. into them. They are simply omitted from the `children` key of the response.
(Although they will still appear in the `children_state`key of the `room`.) (Although they will still appear in the `children_state` key of the `room`.)
Similarly, if a server-set limit on the size of the response is reached, additional Similarly, if a server-set limit on the size of the response is reached, additional
rooms are not added to the response and can be queried individually. rooms are not added to the response and can be queried individually.
@ -245,10 +250,13 @@ For both the `room` and `children` fields the summary of the room/space includes
the fields returned by `/publicRooms` (see [spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)), the fields returned by `/publicRooms` (see [spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)),
with the addition of: with the addition of:
* **`room_type`**: the value of the `m.type` field from the room's `m.room.create` * **`room_type`**: the value of the `type` field from the room's `m.room.create`
event, if any. event, if any.
* **`allowed_room_ids`**: A list of room IDs which give access to this room per * **`allowed_room_ids`**: A list of room IDs which give access to this room per
[MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083).<sup id="a1">[1](#f1)</sup> [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083).<sup id="a1">[1](#f1)</sup>
Optional if would be empty.
* **`children_state`**: As per Client-Server API version, though only on the `room`
and not `children`.
#### Example request: #### Example request:
@ -269,13 +277,14 @@ requesting server is not allowed to access the room.
of "default ordering of siblings in the room list" using the `order` key: of "default ordering of siblings in the room list" using the `order` key:
> Rooms are sorted based on a lexicographic ordering of the Unicode codepoints > Rooms are sorted based on a lexicographic ordering of the Unicode codepoints
> of the characters in `order` values. Rooms with no `order` come last, in > of the characters in `order` values. Rooms with no `order` come last with no
> ascending numeric order of the `origin_server_ts` of their `m.room.create` > effective `order`. When the `order` (or lack thereof) is the same, the rooms
> events, or ascending lexicographic order of their `room_id`s in case of equal > are sorted in ascending numeric order of the `origin_server_ts` of their
> `origin_server_ts`. `order`s which are not strings, or do not consist solely > `m.room.create` events, or ascending lexicographic order of their `room_id`s
> of ascii characters in the range `\x20` (space) to `\x7F` (~), or consist of > in case of equal `origin_server_ts`. `order`s which are not strings, or do
> more than 50 characters, are forbidden and the field should be ignored if > not consist solely of ascii characters in the range `\x20` (space) to `\x7E`
> received. > (`~`), or consist of more than 50 characters, are forbidden and the field
> should be ignored if received.
Unfortunately there are situations when a homeserver comes across a reference to Unfortunately there are situations when a homeserver comes across a reference to
a child room that is unknown to it and must decide the ordering. Without being a child room that is unknown to it and must decide the ordering. Without being
@ -306,16 +315,8 @@ request a space summary for Room D, but this is undesirable:
* If we expand the example above to many rooms than this becomes expensive to * If we expand the example above to many rooms than this becomes expensive to
query a remote server simply for ordering. query a remote server simply for ordering.
This proposes changing the ordering rules from MSC1772 to the following: This proposes changing the ordering rules from MSC1772 to consider the `m.space.child`
event instead of the `m.room.create` event.
> Rooms are sorted based on a lexicographic ordering of the Unicode codepoints
> of the characters in `order` values. Rooms with no `order` come last, in
> ascending numeric order of the `origin_server_ts` of their `m.space.child`
> events, or ascending lexicographic order of their `room_id`s in case of equal
> `origin_server_ts`. `order`s which are not strings, or do not consist solely
> of ascii characters in the range `\x20` (space) to `\x7E` (~), or consist of
> more than 50 characters, are forbidden and the field should be ignored if
> received.
This modifies the clause for calculating the order to use the `origin_server_ts` This modifies the clause for calculating the order to use the `origin_server_ts`
of the `m.space.child` event instead of the `m.room.create` event. This allows of the `m.space.child` event instead of the `m.room.create` event. This allows

Loading…
Cancel
Save