Skip to content

Conversation

@nexy7574
Copy link

@nexy7574 nexy7574 commented Oct 29, 2025

Rendered

N.B. Assuming MSC ID 4375 as #4374 was marked as spam.


Implementations:

Signed-off-by: Nexus [email protected]

@nexy7574 nexy7574 changed the title MSCXXXX: Admin Room Management MSC4375: Admin Room Management Oct 29, 2025
@nexy7574 nexy7574 changed the title MSC4375: Admin Room Management [WIPMSC4375: Admin Room Management Oct 29, 2025
@nexy7574 nexy7574 changed the title [WIPMSC4375: Admin Room Management [WIP] MSC4375: Admin Room Management Oct 29, 2025
Copy link

@TheArcaneBrony TheArcaneBrony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry Nex, got quite a bit of thoughts on a few points, especially relating to how i'm using the existing equivalents and findings from Rory&::MatrixUtils, hope you don't mind :)

Comment on lines 59 to 62
- `POST /_matrix/client/v1/admin/rooms/{roomID}/evacuate` - Start removing all local users from a room
- `GET /_matrix/client/v1/admin/rooms/{roomID}/evacuate/status` - Get room evacuation status
- `PUT /_matrix/client/v1/admin/rooms/{roomID}/blocked` - Block or unblock a room
- `DELETE /_matrix/client/v1/admin/rooms/{roomID}` - Start purging a room

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be a single endpoint? I'm not sure I see much value in having these be separate, and feels like a lot of room for error. (ie. what happens if you call /evacuate and then DELETE immediately afterwards? what happens if you PUT /blocked on a room that isnt known (/anymore)? What happens if you delete a room without evacuation?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single endpoint doesn't feel very RESTful but there are definitely too many endpoints. I'll see if I can squash some of them down into /remove and /block

Comment on lines +89 to +95
| `exclude_empty` | boolean | `false` | Exclude rooms with zero local members |
| `exclude_private` | boolean | `false` | Exclude rooms with a join rule other than `public` |
| `exclude_public` | boolean | `false` | Exclude rooms with a join rule of `public` |
| `exclude_encrypted` | boolean | `false` | Exclude encrypted rooms |
| `exclude_unencrypted` | boolean | `false` | Exclude unencrypted rooms |
| `exclude_federated` | boolean | `false` | Exclude rooms where `m.federate` is missing or `true` |
| `exclude_unfederated` | boolean | `false` | Exclude rooms where `m.federate` is `false` |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels kind of backwards. A common thing i do is explicitly list empty rooms, or ie. tombstoned rooms.

Could these be tristates instead? ie. empty=true/false or omitted, also merging un/encrypted and un/federated.

Another nice touch would be able to filter for tombstoned rooms in particular (something i do as a maintenance task from time to time)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tristates are ugly and i dont like them but at this point might be a necessary evil

Comment on lines +98 to +107
**Order by** can be any of the following:
- `name`: Sort by room name in [lexographical order](https://en.wikipedia.org/wiki/Lexicographic_order).
Rooms with no `m.room.name` event are considered to have an empty string for a room name.
- `local_members`: Sort by local member count in descending order (largest -> smallest).
- `total_members`: Sort by total member count in descending order (largest -> smallest).
- `created_at`: Sort by the room creation timestamp (newest -> oldest).
- `room_version`: Sort by the room version (oldest -> newest). Unstable room versions are treated as
"newest", and sorted lexographically.
- `latest_event`: Sort by the timestamp of the latest received event (oldest -> newest). Events that
soft-failed SHOULD be considered in the sorting.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been a huge annoyance with the synapse admin API here: can we make these consistent? It gets quite annoying when you have to remember which ones are backwards when you're explicitly exposing "Ascending"/"Descending" in your UI (directly mapped to dir=f/b).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'll make room_version newest -> oldest, and change latest_event to last_event and make it newest -> oldest also. That good?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would sure be an improvment - though that'd still have name be backwards, no?

Comment on lines 109 to 110
Servers MUST NOT allow `limit`s greater than 500, and MUST wrap the provided `limit` to the cap
if it exceeds it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels kind of low, personally. In RMU, I query in pages of 1000, used to be 250 but was changed at some point due to performance issues regarding how little data synapse actually returns)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if venturing past 100 is even something people are willing to accept. The original proposal did suggest a 1000 cap, but I'd like other's input on how much is acceptable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worthwhile to allow larger page sizes, especially with sql based servers that would have to SELECT * FROM rooms ORDER BY property ASCENDING SKIP 15200 LIMIT 100 otherwise - that's a lot of overhead spent on just skipping rows

Comment on lines +114 to +123
#### Response

**200 OK**: The sever successfully processed the request.

```jsonc
{
"end": "...", // a pagination token represending the end of the chunk, if any.
"chunk": ["!room1", "!room2", "..."], // an array of room IDs the server knows about
}
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems horribly inefficient: for ~10k rooms, this would mean 22 list requests and 10k room details requests to render a table of rooms (without pagination, as done in Rory&::MatrixUtils), but most likely also common in ie. bots.

Could this endpoint return something more like a summary of each room, like how synapse does it, or whatever the room info endpoint returns?

Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fields I'd include would be the create event, the admin information about the room (block status etc), and then maybe some cached information. It'd be good to keep these under distinct properties as the data is from different sources and some of it is just derived from a cached summary of current room state.

{
  "room_id": "...",
  "admin_info": "...",
  "create_event": "...",
  "current_state": "..."
}

Each property in current_state may be undefined and unavailable. create_event and admin_info should be required. Because they should always be available before presenting a room to the API1.

  • invited: The number of m.room.member events with a membership of invite in the room. This is useful for spotting rooms with lots of invitations being sent to them.
  • joined: The total number of m.room.member events with a membership of join. Useful for gauging the size of the room for triaging reports or action.
  • join_rules: It may be important to determine whether the room is private or public
  • m.room.name : Important to have access to the room name content. Included rather than the field in case the event changes later but probably doesn't matter here
  • m.room.topic: Important to have access to the topic event content https://spec.matrix.org/latest/client-server-api/#mroomtopic. This one does need to be the content since now it is extensible and has rich text.
  • m.room.avatar: https://spec.matrix.org/latest/client-server-api/#mroomavatar

Footnotes

  1. Otherwise there are big security implications.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be better as "room_summary" for things like the invited and joined membership counts. And then just use current state as something that can be filled in with a filter.

Comment on lines +170 to +172
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `include_members` | boolean | `false` | Include **all** room membership events |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For most rooms, wouldn't it make more sense to just make this return all room state instead? That seems more useful than just including members.

Comment on lines +478 to +479
// The user ID to give the new power level to. Defaults to the authenticated user.
// MUST be local to the current homeserver.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requiring the user to be local kind of feels like a needless restriction - and is something that is used quite often on the synapse admin API.

Notably: I use this quite often through Draupnir, to gain temporary access to do x or y. This draupnir instance isn't always hosted on my homeserver, and this would be a major restriction for me.


Rooms are the lifeblood of Matrix, so this proposal introduces some incredibly powerful endpoints.
Misuse of the powers granted can result in permanent data loss (namely in the event of local-only
rooms being purged) and potentially irreversible privilage escalation via takeovers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/remove_admin endpoint?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't resolve the problem

| Stable | Unstable |
| `/_matrix/client/v1/` | `/_matrix/client/unstable/uk.timedout.msc4375/` |

Servers SHOULD advertise support for this functionality via `/_matrix/client/versions`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be advertised via /capabilities instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, unlike the user suspend/lock endpoints, you either do or don't have access to these endpoints, not only some of them. Once it becomes stable you can just try the endpoints with the assumption they'll fail if you're not a server admin.
The only thing I can see this being useful for is allowing clients to pre-determine their admin status via capabilities for rendering, but rooms aren't usually managed via a client UI so this seems like more of an edge case.

The versions advertisement is only while it is unstable, I highlighted the "OPTIONALLY only to authenticated users." to remind people that the endpoint can return different information for authenticated users

Comment on lines +98 to +108
**Order by** can be any of the following:
- `name`: Sort by room name in [lexographical order](https://en.wikipedia.org/wiki/Lexicographic_order).
Rooms with no `m.room.name` event are considered to have an empty string for a room name.
- `local_members`: Sort by local member count in descending order (largest -> smallest).
- `total_members`: Sort by total member count in descending order (largest -> smallest).
- `created_at`: Sort by the room creation timestamp (newest -> oldest).
- `room_version`: Sort by the room version (oldest -> newest). Unstable room versions are treated as
"newest", and sorted lexographically.
- `latest_event`: Sort by the timestamp of the latest received event (oldest -> newest). Events that
soft-failed SHOULD be considered in the sorting.

Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly recommend against copying the synapse room list ordering because it is "gappy". Rooms discovered or created during pagination may be missed by the consumer. And if they want to keep looking for new rooms, they have to start over the entire stream again.

What I do recommend instead is to have the streaming token be based on "room encounter time". Where a room encounter is defined as when we have received the m.room.create PDU for for the first time. (Which in turn is triggered by things like receiving an event or an invitation from the room for the first time)

Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then consumers can just paginate the entire list, and then poll forwards until more rooms are discovered by the homeserver.

Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might still be possible to provide custom orders ontop of this if the canonical order still is the room encounter time. And you just paginate with a custom order within a limit. e.g. /rooms?dir=f&to=token_obtained_when_pagination_session_started&order_by=member_count

Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That way they can follow up with another pagination session /rooms?dir=f&to=new_token_obtained_when_this_pagination_session_started&from=to_token_from_previous_session_example&order_by=member_count with no gaps.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, the pagination token could just be a timestamp, which would still allow custom ordering 🤔

Copy link
Contributor

@Gnuxie Gnuxie Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a distinct problem that is related to this is that ordering by mutable fields in pagination always leads to a problem where entries can disappear or appear more than once throughout the session. I think we just have to accept that and document the phenomenon as part of the endpoint. Most of the orders offered by the synapse admin api are on mutable fields and are unstable. So I will be happy with just a stable order by encounter time being offered for tools like Draupnir that need to be able to collect the entire list.

Or in other words, there's no need to rebase the other orders on encounter time, because that wouldn't provide stability anyway as they are mutable sorts.

```jsonc
{
"end": "...", // a pagination token represending the end of the chunk, if any.
"chunk": ["!room1", "!room2", "..."], // an array of room IDs the server knows about
Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to also be safer for pre-v12 rooms and reference rooms in terms of their m.room.create event id rather than their room id.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this does solve the issue of purging rooms with custom room IDs that tend to run into url encoding issues

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the problem of admins possibly not knowing the create event ID,

That's kinda irrelevant if this proposal is updated to go using create event IDs instead of room IDs in the first place but I don't really fancy doing that

Comment on lines +208 to +209
Evacuate also has the capability to replace a room with a new one, to inform users that they were
evacuated.
Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know Synapse has this, but I don't know if we need to provide parity here. We should wait until we have a more general way for server admins to communicate with users that can be utilised.

The drawback of doing this without another way of communicating the evacuation is users being confused why a room they were in no longer exists...

@@ -0,0 +1,534 @@
# MSC4375: Admin Room Management
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the room summary and room pagination apis could be split out if it's found that the evacuate/block/takeover semantics are going to require more thought and work. I am willing to champion room pagination and summary.


TODO

## Alternatives
Copy link
Contributor

@Gnuxie Gnuxie Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the-draupnir-project/Draupnir#989 room locking and suspension might be nice to have. Happy to follow up on these.

@turt2live turt2live added proposal A matrix spec change proposal client-server Client-Server API kind:feature MSC for not-core and not-maintenance stuff needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. safety labels Oct 29, 2025

Every Matrix server supports rooms, big or small. What every server doesn't support, however, is the
ability to manage the rooms that their server is participating in. Synapse has
[the Synapse admin API][s1], Dendrite has [the Dendrite admin API][d1], Conduit and its derrivatives
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you put a lot of hard work into this MSC already but I think the proper course here is to standardize the Synapse admin API. There are a few reasons for this. It is the tradition for standards development: take something that exists or is proven and stabilize, refine details if necessary, and standardize it. An ecosystem of software already exists for the Synapse API, including bots and even several GUI interfaces used by industry. It's much more economical to reuse rather than reinvent. I think the effort and energy is good here, but it should be directed at a hard target and not outer space.

Also one other nit, it was Construct that invented the admin room 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Synapse admin api is broken and an abomination. No.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stabilize, refine details if necessary, and standardize it.

That's effectively what I'm trying to do here. I took parts of the Synapse API I liked, parts of the Dendrite API that I liked, and the UX parts of Conduit's admin commands that I liked, and tried to combine them into a middle-ground collection of routes.

The idea behind this MSC, MSC4323, and a couple other MSCs I've got in the oven, is to move away from each implementation's own versions of these APIs, to bring one uniform, standardised API that anyone can both implement and use without needing to worry about the intricacies. Part of this includes building off of prior art, rather than simply standardising it. All of the implementations right now have issues, and this proposal no doubt also will (this first draft is also more of a rough sketch than anything meant to be final), but the great thing about the spec is that anyone can review proposals, and if they don't like things that get merged into spec, they can open up followup proposals to fix what they don't like, whereas implementation specific interfaces don't tend to get that same TLC.

An ecosystem of software already exists for the Synapse API, including bots and even several GUI interfaces used by industry. It's much more economical to reuse rather than reinvent

This would require cloning the API routes verbatim, which would mean they aren't even under /_matrix at root. Even if I were to just clone Synapse's API, there'd still need to be plenty of changes to make it spec-ready.

Also one other nit, it was Construct that invented the admin room 😉

Caught red handed not doing my homework :^)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client-server Client-Server API hacktoberfest-accepted kind:feature MSC for not-core and not-maintenance stuff needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. proposal A matrix spec change proposal safety

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants