MSC4354: Sticky Events#4354
Conversation
It wasn't particulalry useful for clients, and doesn't help equivocation much.
Co-authored-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Co-authored-by: Johannes Marbach <n0-0ne+github@mailbox.org>
|
Just a note: Trixnity does implement this MSC with (as usual) high test coverage here: https://gitlab.com/connect2x/trixnity/trixnity/-/merge_requests/687 This does not mean, that I like how the MSC is right now (see my review). |
|
This proposal should supercede #4357 |
Tested on NetBSD 9 amd64 by reporting pyproject.toml buglets upstream! # Synapse 1.148.0 (2026-02-24) ## Features - Support sending and receiving [MSC4354 Sticky Event](matrix-org/matrix-spec-proposals#4354) metadata. ([\#19365](element-hq/synapse#19365)) ## Deprecations and Removals - Remove support for [MSC3244: Room version capabilities](matrix-org/matrix-spec-proposals#3244) as the MSC was rejected. ([\#19429](element-hq/synapse#19429))
| MatrixRTC currently relies on allowing any user (PL0) to send `org.matrix.msc3401.call` | ||
| and `org.matrix.msc3401.call.member` state events into the room |
There was a problem hiding this comment.
I think a link to #3401 could help to provide some context here.
| MatrixRTC currently relies on allowing any user (PL0) to send `org.matrix.msc3401.call` | |
| and `org.matrix.msc3401.call.member` state events into the room | |
| MatrixRTC currently relies on allowing any user (PL0) to send the `org.matrix.msc3401.call` | |
| and `org.matrix.msc3401.call.member` state events from | |
| [MSC3401](https://github.com/matrix-org/matrix-spec-proposals/pull/3401) into the room |
| 1. Any user can modify other user's call state. MSC3757 tries to fix this, but in order to ensure other | ||
| users are unable to modify each other’s state, it proposes using string packing for authorization which | ||
| feels wrong, given the structured nature of events. |
There was a problem hiding this comment.
Just linkifying MSC3757.
| 1. Any user can modify other user's call state. MSC3757 tries to fix this, but in order to ensure other | |
| users are unable to modify each other’s state, it proposes using string packing for authorization which | |
| feels wrong, given the structured nature of events. | |
| 1. Any user can modify other user's call state. [MSC3757](https://github.com/matrix-org/matrix-spec-proposals/pull/3757) | |
| tries to fix this, but in order to ensure other users are unable to modify each other’s state, it proposes | |
| using string packing for authorization which feels wrong, given the structured nature of events. |
| Sending these old events will potentially increase the number of forward extremities in the room for the receiving server. This may impact state resolution | ||
| performance if there are many forward extremities. Servers MAY send dummy events to remove forward extremities (Synapse has the | ||
| option to do this since 2019). Alternatively, servers MAY choose not to add old sticky events to their forward extremities, but | ||
| this A) reduces eventual delivery guarantees by reducing the frequency of transitive delivery of events, B) reduces the convergence | ||
| rate when implementing ephemeral maps (see "Addendum: Implementing an ephemeral map"), as that relies on servers referencing sticky | ||
| events from other servers. |
There was a problem hiding this comment.
Just linking to the docs of said Synapse option.
| Sending these old events will potentially increase the number of forward extremities in the room for the receiving server. This may impact state resolution | |
| performance if there are many forward extremities. Servers MAY send dummy events to remove forward extremities (Synapse has the | |
| option to do this since 2019). Alternatively, servers MAY choose not to add old sticky events to their forward extremities, but | |
| this A) reduces eventual delivery guarantees by reducing the frequency of transitive delivery of events, B) reduces the convergence | |
| rate when implementing ephemeral maps (see "Addendum: Implementing an ephemeral map"), as that relies on servers referencing sticky | |
| events from other servers. | |
| Sending these old events will potentially increase the number of forward extremities in the room for the receiving server. This may impact state resolution | |
| performance if there are many forward extremities. Servers MAY send dummy events to remove forward extremities (Synapse has the | |
| [option] to do this since 2019). Alternatively, servers MAY choose not to add old sticky events to their forward extremities, but | |
| this A) reduces eventual delivery guarantees by reducing the frequency of transitive delivery of events, B) reduces the convergence | |
| rate when implementing ephemeral maps (see "Addendum: Implementing an ephemeral map"), as that relies on servers referencing sticky | |
| events from other servers. | |
| [option]: https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html?highlight=dummy#dummy_events_threshold |
| The lack of historical room key sharing may make some encrypted sticky events undecryptable when new users join the room. | ||
| [MSC4268: Sharing room keys for past messages](https://github.com/matrix-org/matrix-spec-proposals/pull/4268) would | ||
| help with this. |
There was a problem hiding this comment.
This MSC has since been merged. So maybe we can reformulate this slightly?
| The lack of historical room key sharing may make some encrypted sticky events undecryptable when new users join the room. | |
| [MSC4268: Sharing room keys for past messages](https://github.com/matrix-org/matrix-spec-proposals/pull/4268) would | |
| help with this. | |
| The prior lack of historical room key sharing that could make some encrypted sticky events undecryptable when new users join the room | |
| has been alleviated with [MSC4268: Sharing room keys for past messages](https://github.com/matrix-org/matrix-spec-proposals/pull/4268). |
|
|
||
| Servers could broadcast different values for the same key to different servers, causing the map to not converge: | ||
| the Byzantine Broadcast problem. Matrix already has a data structure to agree on shared state: the room DAG. | ||
| As such, this led to the prototype to the current proposal. By putting the data into the DAG, other servers |
There was a problem hiding this comment.
I think there is some duplication here?
| As such, this led to the prototype to the current proposal. By putting the data into the DAG, other servers | |
| As such, this led to the current proposal. By putting the data into the DAG, other servers |
| `(room_id, sender, type, content.sticky_key)` to track the current values in the map. Nothing stops | ||
| users sending multiple events with the same `sticky_key`. To deterministically tie-break, clients which |
There was a problem hiding this comment.
I think in the 4-uple both need to be identical to require tie-breaking?
| `(room_id, sender, type, content.sticky_key)` to track the current values in the map. Nothing stops | |
| users sending multiple events with the same `sticky_key`. To deterministically tie-break, clients which | |
| `(room_id, sender, type, content.sticky_key)` to track the current values in the map. Nothing stops | |
| users sending multiple events with the same event type and `sticky_key`. To deterministically tie-break, clients which |
| send another sticky event with just `content.sticky_key` set, with all the other application-specific fields omitted. Redacting | ||
| sticky events are an alternative way to do this, although this loses the `content.sticky_key` property so clients will need to |
There was a problem hiding this comment.
I think this should be "is" rather than "are"?
| send another sticky event with just `content.sticky_key` set, with all the other application-specific fields omitted. Redacting | |
| sticky events are an alternative way to do this, although this loses the `content.sticky_key` property so clients will need to | |
| send another sticky event with just `content.sticky_key` set, with all the other application-specific fields omitted. Redacting | |
| sticky events is an alternative way to do this, although this loses the `content.sticky_key` property so clients will need to |
| as having [left the session](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#leaving-a-session). | ||
| This is conceptually the same as deleting a key from the map. However, as the server is unaware of the `sticky_key`, it | ||
| cannot perform the delete operation for clients, and will instead send the empty content event down `/sync`. This means if | ||
| N users leave a call, there will be N sticky events present in `/sync` for the sticky duration specified. |
There was a problem hiding this comment.
They will only occur once in /sync though given what the proposals says further up?
Sticky events follow the same 'stream-like' behaviour as the
timeline. This means clients will receive a sticky
event S once, and subsequent requests with an advancedsincetoken will not return the same sticky event S.
| ## Addendum | ||
|
|
||
| This section explains how sticky events can be used to implement a short-lived, per-user, per-room key-value store. | ||
| This technique would be used by MatrixRTC to synchronise RTC members, and should land in the spec as a suggested algorithm to follow. |
There was a problem hiding this comment.
I think we should downgrade this from a RECOMMENDATION to an example. The concrete keying semantics seem highly use case specific to me. The 4-uple (room_id, sender, type, content.sticky_key) and the tie-breaking logic outlined below may work for Matrix RTC. Other applications might have quite different requirements though. Specifying the local aggregation of sticky events should be left to features / MSCs using sticky events.
CC @toger5 with whom I chatted about this at the Matrix Community Summit.
There was a problem hiding this comment.
I would argue that the quadrupel is similar to the triple of a state event and application agnostic. I know, that there are other opinions than mine, but I still think, that sticky events are technically "just" temporary member-controlled state events and then it make sense to have rules like this on how they are working and stored locally.
There was a problem hiding this comment.
[...] I still think, that sticky events are technically "just" temporary member-controlled state events [...]
I think that is exactly the question actually. Do we design sticky events as events with a state-event-like delivery guarantee and let applications do with them whatever they want? Or do we design them as state-like events with delivery guarantee and map-like semantics?
It feels like MatrixRTC is going to be the only user of this feature for a while. So I'm leaning towards treating the map logic as a MatrixRTC specialty first from where we could easily generalize it to arbitrary event types later when we find other use cases.
There was a problem hiding this comment.
Just because no other use cases are known doesn’t mean there aren’t any. Ultimately, it will have to be implemented for MatrixRTC anyway. I can’t see any reason why we shouldn’t make it available here as a general feature. Otherwise, applications will start misusing MatrixRTC for this purpose because it wasn’t defined as a general feature. From an implementation (SDK) perspective it is much closer to sticky events than to MatrixRTC: https://gitlab.com/connect2x/trixnity/trixnity/-/merge_requests/687
There was a problem hiding this comment.
Just because no other use cases are known doesn’t mean there aren’t any.
Yes, true. I just think we may want to be cautious with prescribing logic for use cases that we don't yet know about because we cannot validate that this logic will actually be suitable.
Ultimately, the only difference in what I'm proposing is an event type filter chucked in front of the local aggregation logic for sticky events so that it is only applied to the Matrix RTC events. It feels easier to extend or remove that filter in future when we find other usages than to go the other way around.
There was a problem hiding this comment.
The spec makes many recommendations for extensibility (e.g. event type naming convention) and things like extensible events are a good example of "this is a convention/recommendation, please do it" where SDK developers know where they stand in terms of reading fields they support. I don't see the sticky event map recommendation as anything novel.
There was a problem hiding this comment.
It seems novel to me because we're prescribing aggregation logic for speculative features, not just naming conventions. I'm personally not convinced that we'll gain much by trying to bake the 4-uple mapping algorithm into SDKs this way. The algorithm itself doesn't appear very complicated to me and if somebody really needs x-uples for their feature, that should be rather trivial to implement.
Anyway, I've made us spend a lot of words on a really small part of the proposal, sorry. 🙈
I'm not going to die on this hill. It just seems unnecessarily limiting to me.
There was a problem hiding this comment.
I'm confused why you think it's limiting. You can use sticky events without content.sticky_key. You're not forced to use ephemeral maps if you're using sticky keys. All it's doing is standardising map-like semantics so future features know they have this primitive they can make use of, which SDKs will already have anyway if they support MatrixRTC. Without this, the map-like semantics can be hidden away behind MatrixRTC and we'd see more MSCs adding the equivalent functionality.
There was a problem hiding this comment.
Maybe this is a nuance of how we'll write it into the spec. It sounded to me like it'll be a SHOULD for clients to maintain the generic map for any event with content.sticky_key.
Just defining it as a primitive (without normative language) and then having MatrixRTC be the only part of the spec that has normative MUSTs to use that primitive seems totally fine to me.
There was a problem hiding this comment.
Trying to conclude on this. How about we clarify the language like this?
| ## Addendum | |
| This section explains how sticky events can be used to implement a short-lived, per-user, per-room key-value store. | |
| This technique would be used by MatrixRTC to synchronise RTC members, and should land in the spec as a suggested algorithm to follow. | |
| ## Addendum | |
| This section explains how sticky events can be used to implement a short-lived, per-user, per-room key-value store. | |
| This technique would be used by MatrixRTC to synchronise RTC members, and should land in the spec as a (non-normative) | |
| primitive with a recommendation to use it for future features. |
| MatrixRTC relies on a per-user, per-device map of RTC member events. To implement this, this MSC proposes | ||
| a standardised mechanism for determining keys on sticky events, the `content.sticky_key` property: |
There was a problem hiding this comment.
We're currently debating whether and how to chain a user's MatrixRTC membership events using relations. The current idea is that when a user joins an RTC session, they post their membership event without a relation. On any subsequent update of the membership event, a relation to the join event is added.
With that design we may not actually need sticky_key and could instead use the event ID of the join event (the related to event).
CC @toger5
There was a problem hiding this comment.
Relations don't work over federation reliably, what exactly is your proposed flow?
There was a problem hiding this comment.
Right now we send a sticky_key as part of the encrypted content and let the client update the events based on that.
As there is no permanent sticky event the same keys can be reused later with different context.
Considering this it might be the more appropriate concept to use relation chains for mutating sticky items instead of a key based system?
The client logic would then automatically be:
- there is only a set of sticky events. (no keyed map)
- some of those items in the set might mutate as the sdk already does with edits (
m.replace) anyway.
The flow:
- join a call: send a new
m.rtc.membersticky event - update your event (voice->video intent or just to not loose stickyness): send a m.rtc.member that
m.replaces the old current one.- clients that have not yet synced immediately get the relation to the original join (which can help/is a different solution for call duration computation)
- clients that already know about the soon expiring sticky event will emit an edit event on the MatrixEvent obj abstraction. (mutate the existing sticky item in the set)
- The expiration can then be done per event. The original event expires but the edit event remains -> sticky event is still part of the set in the edited state. Which is another way to implement the current sticky expiration logic. (which might be simpler)
I think over federation this is equally stable as the sticky_key approach.
I think it is in general almost equivalent to the sticky_key approach but
- sticky key =
event_idORm.relates_to.event_id - the sticky key is not encrypted anymore but still the metadata is blurred as we dont know if a relation is a m.room.message edit of a sticky m.room.message event or a call update.
There was a problem hiding this comment.
Yeah, I think over federation it should work the same way sticky_key does given that both are transmitted as part of the event JSON.
If we're already using relations to chain the events anyway, that would spare us introducing another random number sticky_key property. The case where it wouldn't work is when the first sticky event already carries another relation (such as m.rtc.notification relating to the sender's m.rtc.member event).
There was a problem hiding this comment.
The case where it wouldn't work is when the first sticky event already carries another relation (such as m.rtc.notification relating to the sender's m.rtc.member event).
If it would not be m.replace but a m.reference relation it should still work as described?
The m.rtc.notification and the m.rtc.member events would be a separate entries in the "sticky set".
There was a problem hiding this comment.
Just for understanding: How would you expect to persist sticky events from an SDK (MatrixRTC agnostic) perspective then? As a flat list? And the MatrixRTC implementation should find out which event "wins"?
There was a problem hiding this comment.
None of the other rules would change. I'm merely suggesting to swap sticky_key with m.relates_to["event_id"] in the algorithm. The same value would still require tie breaking using the rules from the proposal:
- pick the one with the highest
origin_server_ts + sticky.duration_ms(last to expire wins),- tie break on the one with the highest lexicographical event ID (A < Z).
There was a problem hiding this comment.
this doesn't leak more than before
This isn't true though? One exposes "this is one sticky event among many" and the other is "this specifically relates to this event right here".
MatrixRTC also currently chains its membership events using relations.
Please be specific. What are "membership events"? m.room.member? RTC members? It's confusing because this thread uses terminology like "use the event ID of the join event".
What is the compound key for RTC members in general? One RTC session per... device? Not specified? Can you actually have multiple sessions on a single device?
chaining the events together with relations also has the benefit that you can query them using the /relations API which could make some operations faster.
Yes, but it baffles me why you'd do this without a use case for historical relations for the same RTC member?
From my position, it sounds very much like "hey, we're using the "membership event" ID as the sticky_key, let's just use relations?" without really thinking through if it makes sense to do. Like, the obvious advantage to this approach is that a gappy sync will return the latest event which is also a sharp edge case: the replace semantics there don't match the replace semantics of sticky events (origin_server_ts + sticky.duration_ms (last to expire wins)), so in some (all?) cases you'd be forced to walk back earlier edits to ensure you are in fact tracking the last to expire event.
There was a problem hiding this comment.
this doesn't leak more than before
This isn't true though? One exposes "this is one sticky event among many" and the other is "this specifically relates to this event right here".
True, yes. I missed that. This isn't ideal but evidently we already accepted this shortcoming for the other usages of relations in the spec such as threads.
MatrixRTC also currently chains its membership events using relations.
Please be specific. What are "membership events"?
m.room.member? RTC members? It's confusing because this thread uses terminology like "use the event ID of the join event".
Apologies for being sloppy with words. What #4143 currently proposes is:
m.rtc.member (initial event / join RTC session)
^ ^
| m.reference | m.reference
| |
m.rtc.member |
(update due to |
transport change |
etc.) |
|
|
|
m.rtc.member
(disconnect
from session)
As per #4143 this is using m.reference but we have been debating switching to m.replace. Conceptually this is the same though. Hopefully that makes it clear.
What is the compound key for RTC members in general? One RTC session per... device? Not specified? Can you actually have multiple sessions on a single device?
#4143 uses the mapping algorithm as specified in the addendum of this proposal. So the compound key is (room_id, sender, type, content.sticky_key). content.sticky_key is a UUID that is determined when sending the initial m.rtc.member member event to join a session. That means you can have multiple session on a single device.
chaining the events together with relations also has the benefit that you can query them using the /relations API which could make some operations faster.
Yes, but it baffles me why you'd do this without a use case for historical relations for the same RTC member?
The use case in MatrixRTC that we were discussing is reconstructing historical sessions, e.g. for a call history archive.
From my position, it sounds very much like "hey, we're using the "membership event" ID as the
sticky_key, let's just use relations?" without really thinking through if it makes sense to do. Like, the obvious advantage to this approach is that a gappy sync will return the latest event which is also a sharp edge case: the replace semantics there don't match the replace semantics of sticky events (origin_server_ts + sticky.duration_ms(last to expire wins)), so in some (all?) cases you'd be forced to walk back earlier edits to ensure you are in fact tracking the last to expire event.
Well I opened this thread with the intention of us collectively thinking through if using relations to eliminate sticky_key is a sensible thing to do. So thank you for your feedback.
@toger5 and I will sync up on this.
There was a problem hiding this comment.
Given that with relations the server has visibility on both the event being sticky and the sticky key (from the relation), what if the server would reject sending sticky events when the sticky duration would make the event expire before the related-to event?
Tested on NetBSD 10 amd64 with 2026Q2 environment. # Synapse 1.156.0 (2026-07-07) ## Features - Expose [MSC4354 Sticky Events](matrix-org/matrix-spec-proposals#4354) over [MSC4186 (Simplified) Sliding Sync](matrix-org/matrix-spec-proposals#4186). ([\#19591](element-hq/synapse#19591)) - Stabilize support for sending ephemeral events to application services, as per [MSC2409](matrix-org/matrix-spec-proposals#2409). Contributed by @jason-famedly @ Famedly. ([\#19758](element-hq/synapse#19758)) - Include `allowed_room_ids` in the `/summary` client-server API response for rooms with restricted join rules, as required by Matrix 1.15. Contributed by @FrenchGithubUser @famedly. ([\#19762](element-hq/synapse#19762)) - [MSC4140: Cancellable delayed events](matrix-org/matrix-spec-proposals#4140): Allow authentication on delayed event management endpoints (such as `/restart`) to bypass ratelimits for unauthenticated requests based on the client IP address. ([\#19794](element-hq/synapse#19794)) - Add new metric `synapse_non_deactivated_user_count` which tracks the number of non-deactivated users in the database, split by `app_service`. ([\#19848](element-hq/synapse#19848)) - The `GET /_matrix/client/unstable/org.matrix.msc1763/retention/configuration` endpoint is now provided when retention is enabled and `experimental_features.msc1763_enabled` is enabled, based on [MSC1763](matrix-org/matrix-spec-proposals#1763). ([\#19853](element-hq/synapse#19853)) - Add experimental support for [MSC4491: Invite reasons in room creation](matrix-org/matrix-spec-proposals#4491). ([\#19874](element-hq/synapse#19874)) # Synapse 1.155.0 (2026-06-16) # Synapse 1.154.0 (2026-06-04) ## Features - Add support for [MSC4452: Preview URL capabilities API](matrix-org/matrix-spec-proposals#4452) which exposes a `io.element.msc4452.preview_url` capability. If `experimental_features.msc4452_enabled` is `true`, the `/_matrix/(client/v1/media|media/v3)/preview_url` endpoint now responds with a 403 status code when the capability is disabled. ([\#19715](element-hq/synapse#19715)) # Synapse 1.153.0 (2026-05-19) ## Features - Make ACLs apply to EDUs per [MSC4163](matrix-org/matrix-spec-proposals#4163). ([\#18475](element-hq/synapse#18475)) - Stabilize [MSC3266: Room summary API](matrix-org/matrix-spec-proposals#3266), removing the experimental config flag `msc3266_enabled`. Contributed by @dasha-uwu. ([\#19720](element-hq/synapse#19720)) - Partial [MSC4311](matrix-org/matrix-spec-proposals#4311) implementation: `m.room.create` is now a required part of stripped `invite_state`/`knock_state` . Contributed by @FrenchGithubUser @famedly. ([\#19722](element-hq/synapse#19722)) - Expose `tombstoned` and `replacement_room` in room details on admin API endpoint `GET /_synapse/admin/v1/rooms/<room_id>`. Contributed by Noah Markert. ([\#19737](element-hq/synapse#19737)) # Synapse 1.152.1 (2026-05-07) # Synapse 1.152.0 (2026-04-28) ## Features - Add a ["Listing quarantined media changes" Admin API](https://element-hq.github.io/synapse/latest/admin_api/media_admin_api.html#listing-quarantined-media-changes) for retrieving a paginated record of when media became (un)quarantined. ([\#19558](element-hq/synapse#19558), [\#19677](element-hq/synapse#19677), [\#19694](element-hq/synapse#19694)) - Advertise [MSC4445](matrix-org/matrix-spec-proposals#4445) sync timeline order in `unstable_features`. ([\#19642](element-hq/synapse#19642)) - Report the Rust compiler version used in the Prometheus metrics. Contributed by Noah Markert. ([\#19643](element-hq/synapse#19643)) - Passthrough 'article' and 'profile' OpenGraph metadata on URL preview requests. ([\#19659](element-hq/synapse#19659)) - Add a way to re-sign local events with a new signing key. ([\#19668](element-hq/synapse#19668)) - Support [MSC4450: Identity Provider selection for User-Interactive Authentication with Legacy Single Sign-On](matrix-org/matrix-spec-proposals#4450). ([\#19693](element-hq/synapse#19693)) - Add experimental support for [MSC4242](matrix-org/matrix-spec-proposals#4242): State DAGs. Excludes federation support. ([\#19424](element-hq/synapse#19424)) - Adds [Admin API](https://element-hq.github.io/synapse/latest/usage/administration/admin_api/index.html) endpoints to list, fetch and delete user reports. ([\#19657](element-hq/synapse#19657)) - Reduce database disk space usage by pruning old rows from `device_lists_changes_in_room`. ([\#19473](element-hq/synapse#19473), [\#19709](element-hq/synapse#19709)) # Synapse 1.151.0 (2026-04-07) ## Features - Add stable support for [MSC4284](matrix-org/matrix-spec-proposals#4284) Policy Servers. ([\#19503](element-hq/synapse#19503)) - Update and stabilize support for [MSC2666](matrix-org/matrix-spec-proposals#2666): Get rooms in common with another user. Contributed by @tulir @ Beeper. ([\#19511](element-hq/synapse#19511)) - Updated experimental support for [MSC4388: Secure out-of-band channel for sign in with QR](matrix-org/matrix-spec-proposals#4388). ([\#19573](element-hq/synapse#19573)) - Stabilize `room_version` and `encryption` fields in the space/room `/hierarchy` API (part of [MSC3266](matrix-org/matrix-spec-proposals#3266)). ([\#19576](element-hq/synapse#19576)) - Introduce a [configuration option](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_authentication_service) to allow using HTTP/2 over plaintext when Synapse connects to Matrix Authentication Service. ([\#19586](element-hq/synapse#19586)) ## Deprecations and Removals - Remove support for [MSC3852: Expose user agent information on Device](matrix-org/matrix-spec-proposals#3852) as the MSC was closed. ([\#19430](element-hq/synapse#19430)) # Synapse 1.150.0 (2026-03-24) ## Features - Add experimental support for the [MSC4370](matrix-org/matrix-spec-proposals#4370) Federation API `GET /extremities` endpoint. ([\#19314](element-hq/synapse#19314)) - [MSC4140: Cancellable delayed events](matrix-org/matrix-spec-proposals#4140): When persisting a delayed event to the timeline, include its `delay_id` in the event's `unsigned` section in `/sync` responses to the event sender. ([\#19479](element-hq/synapse#19479)) - Expose [MSC4354 Sticky Events](matrix-org/matrix-spec-proposals#4354) over the legacy (v3) /sync API. ([\#19487](element-hq/synapse#19487)) - When Matrix Authentication Service (MAS) integration is enabled, allow MAS to set the user locked status in Synapse. ([\#19554](element-hq/synapse#19554))
Rendered
SCT Stuff:
FCP tickyboxes
MSC checklist