initial version of event relationship MSC
parent
b770cfee8b
commit
9e8460f58c
@ -0,0 +1,255 @@
|
|||||||
|
# MSCxxxx: Event relationships
|
||||||
|
|
||||||
|
It's common to want to send events in Matrix which relate to existing events -
|
||||||
|
for instance, reactions, edits and even replies/threads.
|
||||||
|
|
||||||
|
This proposal is one in a series of proposals that defines a mechanism for
|
||||||
|
events to relate to each other. Together, these proposals replace
|
||||||
|
[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849).
|
||||||
|
|
||||||
|
* This proposal defines a standard shape for indicating events which relate to
|
||||||
|
other events.
|
||||||
|
* [MSCxxxx](https://github.com/matrix-org/matrix-doc/pull/xxxx) defines APIs to
|
||||||
|
let the server calculate the aggregations on behalf of the client, and so
|
||||||
|
bundle the related events with the original event where appropriate.
|
||||||
|
* [MSCxxxx](https://github.com/matrix-org/matrix-doc/pull/xxxx) defines how
|
||||||
|
users can edit messages using this mechanism.
|
||||||
|
* [MSCxxxx](https://github.com/matrix-org/matrix-doc/pull/xxxx) defines how
|
||||||
|
users can annotate events, such as reacting to events with emoji, using this
|
||||||
|
mechanism.
|
||||||
|
|
||||||
|
## Proposal
|
||||||
|
|
||||||
|
This proposal introduces the concept of relations, which can be used to
|
||||||
|
associate new information with an existing event.
|
||||||
|
|
||||||
|
Relations are any event which have an `m.relates_to` field in their
|
||||||
|
contents. The `m.relates_to` field must include a `rel_type` field that
|
||||||
|
gives the type of relationship being defined, and the `event_id` field that
|
||||||
|
gives the event which is the target of the relation. All the information about
|
||||||
|
the relationship lives under the `m.relates_to` key.
|
||||||
|
|
||||||
|
If it helps, you can think of relations as a "subject verb object" triple,
|
||||||
|
where the subject is the relation event itself; the verb is the `rel_type`
|
||||||
|
field of the `m.relates_to` and the object is the `event_id` field.
|
||||||
|
|
||||||
|
We consciously do not support multiple different relations within a single event,
|
||||||
|
in order to keep the API simple, and in the absence of identifiable use cases.
|
||||||
|
Instead, one would send multiple events, each with its own `m.relates_to`
|
||||||
|
defined.
|
||||||
|
|
||||||
|
A `rel_type` of `m.reference` is defined for future handling replies and
|
||||||
|
threading. This let you define an event which references an existing
|
||||||
|
event. When aggregated, this currently doesn't do anything special, but in
|
||||||
|
future could bundle chains of references (i.e. threads). These do not yet
|
||||||
|
replace the [reply mechanism currently defined in the spec](https://matrix.org/docs/spec/client_server/latest#rich-replies).
|
||||||
|
|
||||||
|
For instance, an `m.room.message` which references an existing event
|
||||||
|
would look like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {
|
||||||
|
"body": "i <3 shelties",
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.reference",
|
||||||
|
"event_id": "$another_event_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Different subtypes of references could be defined through additional fields on
|
||||||
|
the `m.relates_to` object, to distinguish between replies, threads, etc.
|
||||||
|
This MSC doesn't attempt to define these subtypes.
|
||||||
|
|
||||||
|
XXX: do we want to support multiple parents for a m.reference event, if a
|
||||||
|
given event references different parents in different ways?
|
||||||
|
|
||||||
|
### Sending relations
|
||||||
|
|
||||||
|
Related events are normal Matrix events, and can be sent by the normal /send
|
||||||
|
API.
|
||||||
|
|
||||||
|
The server should postprocess relations if needed before sending them into a
|
||||||
|
room, as defined by the relationship type. For example, a relationship type
|
||||||
|
might only allow a user to send one related message to a given event.
|
||||||
|
|
||||||
|
Similar to membership events, a convenience API is also provided to highlight
|
||||||
|
that the server may post-process the event, and whose URL structures the
|
||||||
|
semantics of the relation being sent more clearly:
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /_matrix/client/r0/rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}/{txn_id}[?key={relation_key}]
|
||||||
|
{
|
||||||
|
// event contents
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `parent_id` is the ID of the event being referenced. In other words, it is
|
||||||
|
the `event_id` field that will be in the `m.relates_to` object.
|
||||||
|
|
||||||
|
The `relation_key` is for relationships that have a `key` property.
|
||||||
|
|
||||||
|
The endpoint does not have any trailing slashes.
|
||||||
|
|
||||||
|
### Receiving relations
|
||||||
|
|
||||||
|
Relations are received during non-gappy incremental syncs (that is, syncs
|
||||||
|
called with a `since` token, and that have `limited: false` in the portion of
|
||||||
|
response for the given room) as normal discrete Matrix events.
|
||||||
|
|
||||||
|
[MSCxxxx](https://github.com/matrix-org/matrix-doc/pull/xxxx) defines ways in
|
||||||
|
which the server may aid clients in processing relations by aggregating the
|
||||||
|
events.
|
||||||
|
|
||||||
|
### End to end encryption
|
||||||
|
|
||||||
|
Since the server has to be able to bundle related events, structural
|
||||||
|
information about relations cannot be encrypted end-to-end, and so the
|
||||||
|
`m.relates_to` field should not be included in the ciphertext.
|
||||||
|
|
||||||
|
A future MSC may define a method for encrypting certain parts of the
|
||||||
|
`m.relates_to` field that may contain sensitive information.
|
||||||
|
|
||||||
|
### Redactions
|
||||||
|
|
||||||
|
Relations may be redacted like any other event. In the case of `m.reference` it
|
||||||
|
removes the referencing event.
|
||||||
|
|
||||||
|
The `m.relates_to`.`rel_type` and `m.relates_to`.`event_id` fields should
|
||||||
|
be preserved over redactions, so that clients can distinguish redacted edits
|
||||||
|
from normal redacted messages, and maintain reply ordering.
|
||||||
|
|
||||||
|
FIXME: synapse doesn't do this yet
|
||||||
|
|
||||||
|
XXX: Does this require a new room version?
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
Can you reply (via m.references) to a [reaction](https://github.com/matrix-org/matrix-doc/pull/xxxx)/[edit](https://github.com/matrix-org/matrix-doc/pull/xxxx)?
|
||||||
|
* Yes, at the protocol level. But you shouldn't expect clients to do anything
|
||||||
|
useful with it.
|
||||||
|
* Replying to a reaction should be treated like a normal message and have the
|
||||||
|
reply behaviour ignored.
|
||||||
|
* Replying to an edit should be treated in the UI as if you had replied to
|
||||||
|
the original message.
|
||||||
|
|
||||||
|
What does it mean to call /context on a relation?
|
||||||
|
* We should probably just return the root event for now, and then refine it in
|
||||||
|
future for threading?
|
||||||
|
* XXX: what does synapse do here?
|
||||||
|
|
||||||
|
Do we need to support retrospective references?
|
||||||
|
* For something like "m.duplicate" to retrospectively declare that one event
|
||||||
|
dupes another, we might need to bundle two-levels deep (subject+ref and then
|
||||||
|
ref+target). We can cross this bridge when we get there though, as a 4th
|
||||||
|
aggregation type
|
||||||
|
|
||||||
|
## Potential issues
|
||||||
|
|
||||||
|
### Federation considerations
|
||||||
|
|
||||||
|
We have a problem with resynchronising relations after a gap in federation:
|
||||||
|
We have no way of knowing that an edit happened in the gap to one of the events
|
||||||
|
we already have. So, we'll show inconsistent data until we backfill the gap.
|
||||||
|
* We could write this off as a limitation.
|
||||||
|
* Or we could make *ALL* relations a DAG, so we can spot holes at the next
|
||||||
|
relation, and go walk the DAG to pull in the missing relations? Then, the
|
||||||
|
next relation for an event could pull in any of the missing relations.
|
||||||
|
Socially this probably doesn't work as reactions will likely drop off over
|
||||||
|
time, so by the time your server comes back there won't be any more reactions
|
||||||
|
pulling the missing ones in.
|
||||||
|
* Could we also ask the server, after a gap, to provide all the relations which
|
||||||
|
happened during the gap to events whose root preceeded the gap.
|
||||||
|
* "Give me all relations which happened between this set of
|
||||||
|
forward-extremities when I lost sync, and the point i've rejoined the DAG,
|
||||||
|
for events which preceeded the gap"?
|
||||||
|
* Would be hard to auth all the relations which this api coughed up.
|
||||||
|
* We could auth them based only the auth events of the relation, except we
|
||||||
|
lose the context of the nearby DAG which we'd have if it was a normal
|
||||||
|
backfilled event.
|
||||||
|
* As a result it would be easier for a server to retrospectively lie about
|
||||||
|
events on behalf of its users.
|
||||||
|
* This probably isn't the end of the world, plus it's more likely to be
|
||||||
|
consistent than if we leave a gap.
|
||||||
|
* i.e. it's better to consistent with a small chance of being maliciously
|
||||||
|
wrong, than inconsistent with a guaranteed chance of being innocently
|
||||||
|
wrong.
|
||||||
|
* We'd need to worry about pagination.
|
||||||
|
* This is probably the best solution, but can also be added as a v2.
|
||||||
|
|
||||||
|
## Tradeoffs
|
||||||
|
|
||||||
|
### Event shape
|
||||||
|
|
||||||
|
Shape of
|
||||||
|
|
||||||
|
```json
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"m.reference": {
|
||||||
|
"event_id": "$another:event.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
versus
|
||||||
|
|
||||||
|
```json
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.reference",
|
||||||
|
"event_id": "$another:event.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The reasons to go with `rel_type` is:
|
||||||
|
* we don't need the extra indirection to let multiple relations apply to a given pair of
|
||||||
|
events, as that should be expressed as separate relation events.
|
||||||
|
* if we want 'adverbs' to apply to 'verbs' in the subject-verb-object triples which
|
||||||
|
relations form, then we apply it as mixins to the relation data itself rather than trying
|
||||||
|
to construct subject-verb-verb-object sentences.
|
||||||
|
* so, we should pick a simpler shape rather than inheriting the mistakes of m.in_reply_to
|
||||||
|
and we have to keep ugly backwards compatibility around for m.in_reply_to
|
||||||
|
but we can entirely separately worry about migrating replies to new-style-aggregations in future
|
||||||
|
perhaps at the same time as doing threads.
|
||||||
|
|
||||||
|
## Historical context
|
||||||
|
|
||||||
|
pik's MSC441 has:
|
||||||
|
|
||||||
|
Define the JSON schema for the aggregation event, so the server can work out
|
||||||
|
which fields should be aggregated.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"type": "m.room._aggregation.emoticon",
|
||||||
|
"content": {
|
||||||
|
"emoticon": "::smile::",
|
||||||
|
"msgtype": "?",
|
||||||
|
"target_id": "$another:event.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These would then aggregated, based on target_id, and returned as annotations on
|
||||||
|
the source event in an `aggregation_data` field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"content": {
|
||||||
|
...
|
||||||
|
"aggregation_data": {
|
||||||
|
"m.room._aggregation.emoticon": {
|
||||||
|
"aggregation_data": [
|
||||||
|
{
|
||||||
|
"emoticon": "::smile::",
|
||||||
|
"event_id": "$14796538949JTYis:pik-test",
|
||||||
|
"sender": "@pik:pik-test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"latest_event_id": "$14796538949JTYis:pik-test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue