Pre-FEP: Quote posts, quote policies and quote controls

Hi!

As you may be aware, Quote Posts are an often-requested feature for Mastodon, but this is something we are careful to implement properly, offering a proper framework for users to consent to being quoted.

Of course, no mechanism will be airtight, there will always be ways to quote someone else, by manually repeating their words, using screenshots and so on. Nevertheless, providing a framework for consent, and adding friction in the absence of consent, is worthwhile. Many existing social network services which feature Quote Posts also offer users at least some control over who can Quote them (on the most basic end, Twitter does not allow people to quote someone who blocked them, while Bluesky, Tumblr and formely the now defunct Cohost allow users to opt out of being quoted, and Bluesky allows to “detach“/revoke individual quotes). We strongly think that such mechanisms are useful, even if there are always ways around them.

Our goal with Quote Posts is allow people to quote users who have consented to it, notifying them in the process, and allowing seamless display and navigation of such quotes. We also want to allow users to later revoke individual quotes if they need to, or change their quoting policy after the fact so that e.g. a post getting viral does not lead to an unmanageable amount of quotes and notifications.

To this end, we are proposing the following draft, which we will shortly adapt into one or more FEPs.

Thanks @trwnh for their reviews and suggestions, particularly around using result and instrument to split concerns and avoid requiring new server-side side-effects.

Keep in mind this is not final. In particular, we are considering re-using GoToSocial’s interaction policies instead of our custom acceptsQuotesFrom property if they get fixed to address our concerns.

There is also an opportunity to change the representation of quotes themselves, though we used the combination of FEP-e232 and https://misskey-hub.net/ns#_misskey_quote rel attribute for compatibility with existing implementations.

FEP-xxxx: Quote posts and quote control

Summary

This is a work-in-progress document describing Mastodon’s proposed way of representing quote posts, users’ choices regarding whether their posts can be quoted and by whom (quote policies), and a mechanism for servers to verify compliance with such policies.

This document describes protocol considerations, which do not necessarily translate directly to User Experience considerations. For instance, the use of the approval mechanism described in this document does not imply that the user’s approval is manual.

Requirements

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this specification are to be interpreted as described in [RFC-2119].

In the remaining of this document, “quoted object” refers to the object being quoted, “original author” to its author, and “quote post” refers to the object quoting the “quoted object”.

Representation of a quote post

A “quote post” is represented as an object with an Object Link (FEP-e232) to a “quoted object” using https://misskey-hub.net/ns#_misskey_quote as a link relation.

Example

{
  "@context": [
    "https://www.w3.org/ns/activitystreams"
  ],
  "type": "Note",
  "id": "https://example.com/users/bob/statuses/1",
  "attributedTo": "https://example.com/users/bob",
  "to": [
    "https://www.w3.org/ns/activitystreams#Public",
    "https://example.com/users/alice"
  ],
  "content": "I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1",
  "tag": [
    {
      "type": "Link",
      "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
      "href": "https://example.com/users/alice/statuses/1",
      "rel": "https://misskey-hub.net/ns#_misskey_quote",
    }
  ]
}

This example is non-normative, and the RE: https://example.com/users/alice/statuses/1 part of the content is an example of textual fallback, but does not otherwise carry meaning. In particular, it does not influence where the embedded quote should be displayed.

Advertising a quote policy

Users may not want their posts to be quoted, or not by everyone. Therefore, we offer a way to declare quote policies using a new acceptsQuotesFrom (http://joinmastodon.org/ns#acceptsQuotesFrom) attribute.

The acceptsQuotesFrom attribute lists Actor and Collection of Actor that can be expected to be able to quote the object.

The author of the object, as well as mentioned users, can always quote the object, regardless of whether they appear in acceptsQuotesFrom.

To advertise a policy of disallowing all quotes, acceptQuotesFrom MUST contain the object author’s identifier as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.

acceptsQuotesFrom SHOULD be restricted to individual actors, the special public collection https://www.w3.org/ns/activitystreams#Public, the author’s followers collection, and the author’s following collection.

Note that the policy is entirely advisory. It SHOULD be used to provide user interface hints such as enabling a “Quote” button or explaining why an object cannot be quoted, but it MUST NOT be used to verify whether a quote post is valid.
See later sections for the actual verification mechanism.

Example

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "toot": "http://joinmastodon.org/ns#",
      "acceptsQuotesFrom": {
        "@id": "toot:acceptsQuotesFrom",
        "@type": "@id"
      }
    }
  ],
  "acceptsQuotesFrom": "https://example.com/users/alice/followers",
  "type": "Note",
  "id": "https://example.com/users/alice/statuses/1",
  "attributedTo": "https://example.com/users/alice",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "content": "I allow my followers to quote this post"
}

Approval stamps

In order to enforce a policy, we rely on approval stamps, a mechanism used to tell third-party servers that a quote is approved, regardless of the current state of the policy.

Quote approval stamps are objects of the type QuoteAuthorization (http://joinmastodon.org/ns#QuoteAuthorization).

The object attribute MUST reference the accepted quote post, the target attribute MUST reference the quoted object, and the attributedTo MUST correspond to the author of the quoted object.

A QuoteAuthorization object MUST be dereferenceable by all parties allowed to see the original post, and MAY be publicly dereferenceable. It MUST NOT embed its object as to avoid possible information leaks. For the same reason, it MUST NOT embed its target object if the server is unable to verify that the party dereferencing the object has permission to see the quoted object.

When a third-party attempts to dereference the QuoteAuthorization, the target MAY be inlined if the third-party has permission to access the quoted object. This is so that the third-party does not have to perform a second request to access the quoted object.

Example of QuoteAuthorization

The following stamp can be used to prove that actor https://example.com/users/alice has accepted https://example.org/users/bob/statuses/1 as a quote of her post https://example.com/users/alice/statuses/1:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "toot": "http://joinmastodon.org/ns#",
      "QuoteAuthorization": "toot:QuoteAuthorization"
    }
  ],
  "type": "QuoteAuthorization",
  "id": "https://example.com/users/alice/stamps/1",
  "attributedTo": "https://example.com/users/alice",
  "object": "https://example.org/users/bob/statuses/1",
  "target": "https://example.com/users/alice/statuses/1"
}

Verifying a QuoteAuthorization

To be considered valid for a particular quote post, the QuoteAuthorization object referenced in quoteApproval MUST satisfy the following properties:

  • its object is the quote post under consideration
  • its target property is the quoted object
  • its attributedTo property is the author of its object
  • the authenticity of the object can be asserted

Revoking a QuoteAuthorization

An approval stamp can be revoked by Deleteing the stamp.

Quote request activity

The Quote (http://joinmastodon.org/ns#Quote) activity type is introduced to request approval for a quote post.

The Quote activity uses the object property to refer to the quoted object, and the instrument property to refer to the quote post.

Example Quote activity

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "toot": "http://joinmastodon.org/ns#",
      "Quote": "toot:Quote"
    }
  ],
  "type": "Quote",
  "id": "https://example.com/users/bob/statuses/1/quote",
  "actor": "https://example.com/users/bob",
  "object": "https://example.com/users/alice/statuses/1",
  "instrument": {
    "type": "Note",
    "id": "https://example.com/users/bob/statuses/1",
    "attributedTo": "https://example.com/users/bob",
    "to": [
      "https://www.w3.org/ns/activitystreams#Public",
      "https://example.com/users/alice"
    ],
    "content": "I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1",
    "tag": [
      {
        "type": "Link",
        "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
        "href": "https://example.com/users/alice/statuses/1",
        "rel": "https://misskey-hub.net/ns#_misskey_quote",
      }
    ]
  }
}

Receiving, accepting or rejecting quote posts

When receiving a Quote activity, the original author decides (either manually or automatically) whether the quote is acceptable. Software that automatically accepts quotes on the author’s behalf should notify the author of such quotes according to their notification settings.

The receiving end MAY inspect the instrument of the Quote itself to decide whether it is acceptable.

If the quote post is considered acceptable, the original author MUST reply with an Accept activity with the Quote activity as its object, and a QuoteAuthorization as its result.

If the quote post is considered unacceptable, the authority SHOULD reply with a Reject activity with the Quote activity as its object.

Example Accept

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "toot": "http://joinmastodon.org/ns#",
      "Quote": "toot:Quote"
    }
  ],
  "type": "Accept",
  "to": "https://example.com/users/bob",
  "id": "https://example.com/users/alice/activities/1234",
  "actor": "https://example.com/users/alice",
  "object": {
    "type": "Quote",
    "id": "https://example.com/users/bob/statuses/1/quote",
    "actor": "https://example.com/users/bob",
    "object": "https://example.com/users/alice/statuses/1",
    "instrument": "https://example.org/users/bob/statuses/1"
  },
  "result": "https://example.com/users/alice/stamps/1"
}

Example Reject

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "toot": "http://joinmastodon.org/ns#",
      "Quote": "toot:Quote"
    }
  ],
  "type": "Reject",
  "to": "https://example.com/users/bob",
  "id": "https://example.com/users/alice/activities/1234",
  "actor": "https://example.com/users/alice",
  "object": {
    "type": "Quote",
    "id": "https://example.com/users/bob/statuses/1/quote",
    "actor": "https://example.com/users/bob",
    "object": "https://example.com/users/alice/statuses/1",
    "instrument": "https://example.org/users/bob/statuses/1"
  }
}

Requesting, obtaining and validating approval

In order to get approval, the quote post author MUST send a Quote (http://joinmastodon.org/ns#Quote) activity to the author of the quoted object, with the quoted object as its object property and the quote post as its instrument.

The quote post SHOULD be inlined in the instrument property and, if not, it SHOULD dereferenceable by the recipient at this point, as the author of the quoted object may want to inspect it to decide whether to accept the quote.

The quote post author MAY wait until they receive an Accept or Reject activity before sending the post’s Create activity to its intended audience.
Doing so is possible for ActivityPub servers that implement the current proposal, and avoids having to issue an Update soon afterwards the Create for the quote post.
It is however not possible to implement for ActivityPub clients, which will likely need to issue a Create before the Quote activity.

Rejection

If the author of the quote post receives a Reject activity from the quoted object’s author to their Quote activity, they MUST consider the quote post to be explicitly rejected.

If the implementation waits for the Accept before issuing a Create, this MAY translate as the inability to publish the quote post.

Otherwise, it MAY translate as a Delete to outright remove the quote post, or an Update to remove the quote part from the post.

Acceptance

If the author of the quote receives an Accept activity, they MUST add a reference to its result in the approvedBy property of the relevant object link.

Depending on whether they already sent a Create activity to the quote post’s intended audience, they SHOULD send a Create activity or an Update activity with the updated approvedBy property.

Example updated Note object

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "gts": "https://gotosocial.org/ns#",
      "approvedBy": {
        "@id": "gts:approvedBy",
        "@type": "@id"
      }
    }
  ],
  "type": "Note",
  "id": "https://example.com/users/bob/statuses/1",
  "attributedTo": "https://example.com/users/bob",
  "to": [
    "https://www.w3.org/ns/activitystreams#Public",
    "https://example.com/users/alice"
  ],
  "content": "I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1",
  "tag": [
    {
      "type": "Link",
      "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
      "href": "https://example.com/users/alice/statuses/1",
      "rel": "https://misskey-hub.net/ns#_misskey_quote",
      "approvedBy": "https://example.com/users/alice/stamps/1"
    }
  ]
}

Verifying third-party quote posts

When processing a quote post from a remote actor, a recipient MUST consider them unapproved unless any of those conditions apply:

  • the author of the quote post and that of the original post are the same (same attributedTo)
  • the author of the quote post is mentioned in the original post
  • approvedBy exists, can be dereferenced and is a valid QuoteAuthorization activity for this object

Revocation of a quote post

Revoking a previously-accepted quote post

The original author may want to perform /a posteriori/ moderation of the quote posts, or block a quote poster in particular.

To do this, the original actor MUST Delete the QuoteAuthorization. They SHOULD send the Delete activity to the quote post’s author and any recipient it has reasons to think has accessed the quote post.

The original author MUST NOT embed the object nor the target of the QuoteAuthorization, so as to avoid potential information leakage.

Handling a revocation

Upon receiving a Delete activity for a previously-verified QuoteAuthorization, third-parties MUST check that the Delete is valid and MUST subsequently consider the quote post unapproved.

Additionally, if the recipient owns the quote post, it MUST forward the Delete to the audience of the quote post.

Opportunistic re-verification of quote approvals

Because getting revocation properly forwarded depends on the good will of the revoked post’s author, it may be necessary to have other means of checking whether an approval has been revoked.

For this reason, recipients SHOULD re-check the approvedBy document when an already-known quote post is accessed for the first time in a given period of time.

Server behavior considerations

This proposal has been made with great care to not require new server behavior, allowing ActivityPub clients to implement this proposal without requiring generic ActivityPub server software to implement additional logic.

In particular, this is the reason the approval stamp is a separate object rather than the Accept itself. Indeed, nothing in the ActivityPub specification would cause a Reject or Undo activity to invalidate the Accept activity itself, which means it would not be suitable as an approval stamp.
While ActivityPub does not technically forbid Accept activities to be the target of a Delete activity, we have found no precedent for that, and we anticipate that deleting activities might not be correctly handled across the fediverse.
For this reason, we opted to use a separate object that can be directly managed by an ActivityPub client, for instance by issuing a Create activity ahead of sending the Accept activity, and that can be deleted with the usual mechanism.

An alternative approach we considered is using a dedicated endpoint to check for approval of a quote. This would effectively allow externalizing approval verification to a separate mechanism, but while this would not require new server behavior, this would still require a new server component to be specified, which is why we opted for the mechanism described in this specification instead.

Security considerations

Servers not implementing this FEP will still be able to quote the post and provide no dogpiling-reducing friction. There is unfortunately nothing we can do about that.

Effectively revoking quote posts relies on the participation of the quote poster’s server to effectively reach the audience of the quote post, so an ill-intentioned server could quote post then refuse to forward the revocation. Still, this can arise on well-intentioned servers too, so the feature remains useful. Opportunistic re-verification of quote approvals should also help.

By not adding a hash or copy of the reply in the QuoteAuthorization object, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the QuoteAuthorization object would have many drawbacks:

  • significantly more complex implementation
  • inability to change the JSON-LD representation after the fact
  • possibly leaking private information if the QuoteAuthorization object is publicly dereferenceable

Implementations

None so far.

References

Copyright

CC0 1.0 Universal (CC0 1.0) Public Domain Dedication

To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.

5 Likes

:+1:

According to ActivityStreams Vocabulary, the domain of target is Activity. I suggest replacing QuoteAuthorization object with QuoteAuthorize activity, or maybe even with a standard Add activity where target is a quotes collection (similar to shares).

If approval stamp is an activity, the approver could Undo it.

I guess this is a typo, and the text should be “to the author of the quoted post”?

In the previous section you talked about Accept(Quote) activity. Does Approve serve a different purpose, or it was supposed to be Accept too?

The use of Accept/Approve and Reject activities appears to be aligned with FEP-5624. There is another approach to managing conversations, which comes from Streams and Hubzilla: Conversation Containers.

The main difference between FEP-5624 and Conversation containers is in the scope of authority. If I understand it correctly, in FEP-5624 each reply is independent and interactions with it are controlled by its author. In Conversation Containers, the whole conversation is managed by the conversation owner (usually the author of a top-level post). This model has several benefits:

  • It enables synchronization of edits, deletions, replies, likes, boosts and other activities between conversation participants. Quote can be synchronized too.
  • Conversation owner can control the scope of the conversation and prevent its widening.
  • Containers can be used to implement groups and circles.

What do you think of it?

In this model, the Quote activity would be Add-ed to a collection activities representing conversation history, instead of being Accept-ed.

We noticed that as well. For some reason I was under the impression that there was prior art in ActivityStreams vocabulary itself with the use of object for Relationship non-activities, but the domain of the object property is correctly (though somewhat messily) defined as being either Activity or Relationship.

We decided to use an object rather than an Activity following a suggestion from @trwnh that “generic” ActivityPub servers might run in more edge cases processing Delete for activities than objects, in addition to that pattern allowing Delete(Delete) which would be pretty weird.

As for using an Add activity, its existence would in itself not be a proof of existence of the item being part of the collection, as a later Remove unknown to the party performing the verification could exist. As for checking presence in a collection, we are not aware of a guaranteed efficient way to do it and we are not sure we want to always expose a list of all quote posts.

ActivityPub defines Undo as undoing side-effects, not removing the activity itself. Here, we would want the stamp to disappear. As for side-effects, we precisely designed the proposal for it not to require new server-side behavior.

Good catch! I’ll fix that! Thanks!

That’s a leftover from a previous version of the draft. This should read Accept, not Approve. I’ll fix that!

Indeed, FEP-5624 was a similar attempt at a tackling a similar problem, but it never went in use, in particular because of the issue of deciding who is the authority on replies. I believe for quotes, the authority being the quoted user is uncontroversial, and quote chains have very different dynamics than reply chains.

I feel like that quotes are a way to bring a new discussion in another context, so it seems at odds with conversation containers, which are about keeping a context across a whole conversation.

1 Like

I agree, activities shouldn’t be Delete-ed. However, some implementations may rely on target being reserved to activities. For example, at some point there was a discussion about “duck typing” activities by checking for ( actor and object) and (actor and target) property combinations. I am not aware of any implementations doing this, but I think it would be prudent to use a different property. Perhaps it could be generator (awkward, but its domain and range include Object)? Or you can just add a warning to the FEP to make implementers aware that such use or target is not recommended by AS.

regarding object and target having a domain of Activity, my suggestion was/is to define new properties specific to the stamp object and its QuoteAuthorization type. there’s no real reason to stick to AS2 Vocab here. if you were to reuse AS2 Vocab then the only properties applicable are context (signaling purpose) and inReplyTo (signaling response). this technically works in a semantic sense, but only in a broad general sense. “the authorization stamp exists in context of the quoted post, and it is a response to the quote post.” is this good enough? you decide.

if not, then the more specific way to describe it is to instead define something like authorizationOf and authorizationScope or whatever names you wanted to bikeshed.

1 Like

I note that early in your document you mention that some of these features will “not necessarily translate directly to User Experience considerations”.

You’ll note that some of my questions below are driven by user experience considerations. It’s my experience that failing to consider the desired end user experience early enough in the process can result in specs that are not suitable.


In the “Representation of a quote post” section the content looks like this:

  "content": "I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1",

What’s the RE: ... text doing there? If the user entered it, doesn’t this undermine the point of quoting the post?

Or are you suggesting the server has appended this to end of the content the user entered?

Edit to add: or this is a marker that indicates where the content of the quoted post should be inserted?


What is the default quote policy (acceptQuotesFrom) if it is not provided?

Since this may depend on the post’s visibility settings and mentions a “default quote policy” may not be possible, and the correct question might be “What is the algorithem for determining the acceptQuotesFrom value if the property is not present?”.


acceptsQuotesFrom appears to be a single property. Shouldn’t it be a list, to allow the author to choose a small number of people who may quote them?


People mentioned in the post can always quote it.

  1. User A sends post 1 mentioning user B.
  2. User B quotes the post in post 2.
  3. User A edits post 1 and removes the mention of user B, and sets acceptsQuotesFrom to disallow quoting.

What happens to post 2 by user B at step 2? At the time it was posted they were authorised to quote it, and there exists, in the edit history, a version of post 1 that does mention them.


How many posts can be listed as “quote posts” on a given top-level post?


In the user agent, are quoted posts expected to be treated like attachments (which do not count against the character limit), or URLs, or something else?


If an account is deleted, what happens to posts that quote posts from that account? Is the permission-to-quote revoked?


Consider a post B that quotes another post A.

The user that posted A revokes the quote permission.

Is the quote Link entirely removed from post B, as if it was never there? Or is some sort of tombstone Link written, so that user agents can provide the context that there used to be something there?

This context (“this post quoted a post that is no longer quotable”) might be necessary to make post B make sense.


Sending a post-with-a-quote and getting approval for the quote can be asynchronous. So someone’s timeline of posts may contain a post with an unapproved quote. What is the intended user experience here?

  • Show the post, with the quote
  • Hide the post
  • Show the post, with the quote, but indicate the quote is unapproved?

Where are user agents supposed to render the quote? Above the text of the post, or below?

Alternatively, why not provide a mechanism in the Note object for the post author to indicate where they would like (on a per-quote basis) the quote to be rendered.


Can I create cycles in the quote graph? For example:

  1. I create Note A. I then update A to quote itself.
  2. I create Note A and B. B quotes A. Then I update A to quote B.
  3. Notes A, B, and C are created. B quotes A, C quotes B. Then A is edited to quote C.

Authors of posts are notified if their post is quoted.

What provision is there to notify authors of the post doing the quoting, if the post they are quoting is modified?


Do quotes of quotes generate notifications?

  • User A sends post 1
  • User B sends post 2, quoting post 1
  • User C sends post 3, quoting post 2

Does user A get a notification about post 3 because it indirectly quotes their post 1?


What properties, if any, should be inherited by the post doing the quoting? For example, if I quote a post marked “sensitive”, is my post also marked sensitive?

If not, what is the intended user-agent behaviour for displaying a non-sensitive post quoting a sensitive post?


Same question, but for content warnings (summary).


I write post A. It is quoted in post B.

Post B is then boosted, favourited, or some other reaction is performed. As the author of post A, does that generate a notification to me?


I may want to quote a very long post, but only refer to part of it (maybe a sentence or two). There is no mechanism here that would allow me to do that. Think of it like setting the focus on an image attachment; you still get the whole attachment, but I control what shows up in the preview.

Edit to add: eg
URL Fragment Text Directives

  1. User A sends post 1, marks it as followers-only (for both visibility and quoting).

  2. User B follows user A, and sends post 2, quoting post 1. They mark post 2 as public.

  3. User C follows user B, but does not follow user A, so they cannot see post 1 from user A.

  4. Post 2 appears in user C’s timeline. What do they see?

Alternatively, at step 2, is user B allowed to send a public post that quotes a post with a narrower quoting visibility? Are they prevented from sending the post entirely? Or is the visibility of their post 2 restricted to just the followers of user A? If so, how is restriction communicated to user B?

1 Like

I write post A. It is quoted in post B.

Post B is then boosted, favourited, or some other reaction is performed.

Do the stats for those actions on post B also accrue to post A? I.e, the author of post A can see “M likes on post, N likes on post 1 that quotes this post, O likes on post 2 that quotes this post” and so on?

I have just discovered an important difference between the FEP-E232
specification and your implementation. Is this intentional? In E232 a link object contains these elements:

"Type": "link",
"mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href": "https://server.example/objects/123",
"name": "RE: https://server.example/objects/123"

Your example has these elements:

 "type": "link",
"mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href": "https://example.com/users/alice/statuses/1",
"rel": "https://misskey-hub.net/ns#_misskey_quote",

The name from E232 (although marked as “optional”) is crucial for a visually nice embedding of the quote in the post. Implementations may replace the value of “name” within the content with the quoted object itself.

I’m not sure about using https://misskey-hub.net/ns#_misskey_quote as a link relation. The original Misskey implementation of quoted posts works differently. It simply uses _misskey_quote as a value in the note to point to the quoted element. (Although off the top of my head I think they now also use quoteUrl, which is used by other implementations.

I suggest concentrating on E232 and omitting this rel parameter. If you want compatibility with other implementations that haven’t implemented E232 yet, sending quoteUrl as an additional value should be enough.

1 Like

I see a discrepancy between the blog post announcing this and the FEP as described here. The blog post reads

You will be able to choose whether your posts can be quoted at all.
You will be notified when someone quotes you.
You will be able to withdraw your post from the quoted context at any time.

But here it is admitted that

Servers not implementing this FEP will still be able to quote the post and provide no dogpiling-reducing friction. There is unfortunately nothing we can do about that.

There is no requirement for servers to implement any specific FEP. Therefor understanding these restrictions is purely optional. E.g. the restrictions can’t say “only the author is allowed to quote this post” (as the wording in the blog post makes it sound), it can only say “if you implement this FEP and aren’t the author, don’t quote this post. Otherwise, feel free to quote”.

The biggest problem I see is that current wording make it sound like any server not understanding the FEP is automatically malicious. But if it’s optional, it’s not malicious to not have it.

I see two ways around this

  • Either accept that it is only a best effort and make it clear that servers not implementing this are not malicious.
  • Don’t send these “more restricted” posts to instances who don’t understand the FEP. E.g. when someone sends a post who shouldn’t be quoted, it could be restricted to only send to actors who have an indication that they understand this FEP’s functionality. In this scenario there are still ways around the quoting, but at least it can then be reasonably considered malicious.

A sends post 1, marked public.

B sends post 2, quotes post 1. B does not follow A.

C follows A and B.

A changes quote controls on post 1 to be followers only.

C views post 2. What do they see?

Is it:

a: the text from post 2, but it’s missing the quote of post 1 because B no longer has permission to quote it?

b: the text from post 2, and the quote of post 1, because C has permission to view both posts?

In the spec-as-written I think the answer is a. And that’s a very weird user experience.

This suggests that quote permissions should not be separate from view permissions. If you can view a post you should be able to quote it. If those view permissions later change other people who can view the quoted post should still be able to see it.

FEP-e232 doesn’t recommend any particular rel parameter because it is intended to support a wide variety of use cases, not just quotes. Some implementers decided to use https://misskey-hub.net/ns#_misskey_quote relation type to indicate that link is actually a quote. That URI was chosen because Misskey is the first (or one of the first) Fediverse platforms to implement quote posts.

I think standardizing that convention in this new proposal is a good idea.

I think there are other factors to consider here:

First, the representation of a “quote post” is separate from the Quote activity, and separate from the QuoteAuthorization stamp. This means that the representation can actually be changed in the future without affecting the Quote/Accept(Quote) flow at all. But for now, given two softwares both understand the same representation, where software A supports the Accept flow and/or stamps, and software B does not, then a “quote post” coming from B is subject to the policies of A on how it wants to handle the authorization/validation aspect:

  • A can show B’s “quote post” as-is
  • A can hide B’s “quote post” in part, or otherwise refuse to treat it as a “quote post”
  • A can fully hide or reject B’s “quote post”
  • (etc)

Essentially, the lack of support or awareness from B is not necessarily malicious and in some ways is not necessarily a significant problem either. What matters is what some third party observer C will do.

Which leads into the second point: the way the stamps are defined to work, they are agnostic to how they are used. The purpose of a stamp is to allow for verification or acknowledgement, in the same way that an official or authority might stamp a piece of paper, a letter, or an envelope in order to signal that it has been approved. But it’s up to you whether you want to recognize the authority of that stamp. There are cases where you might not even check the stamp – say for example that your policy allows mentioned users to quote without needing a stamp. Again, it comes down to policy.

The question then is about whether any given servers share the same policy. If they do, great! If not, then there might be social issues that arise because of that. And the difference between malice and ignorance is irrelevant to that situation. Remember when Wildebeest would leak “DMs” because they only understood “Public”? That’s a more drastic example, but the general principle here is that even if some remote server is overly naive or unaware, then you still have to deal with their behavior. So yes, it’s “best effort”, but so is everything else on the network. Perhaps the language of the blog post can point this out more explicitly, that “can be quoted” is a function of whether the observer validates or not.

It could also be somewhat avoided in the case where two given servers do not share the same representation of “quote posts”. But again, that kind of disparity in functionality is also quite normal on the network.

Does a quote post start a new top level thread?

Or are quote posts to be treated as if they were direct replies to the post they are quoting?

How should a user reallow quotes?

User A sends a post with their default policy allowing anyone to quote it.

It starts going viral, so they decide to temporarily revoke the quotes and restrict who can quote it.

After a week things have died down. They can change their quote policy to allow new quotes. How do they inform the network that the previous quotes are now valid?

What’s the UX expectation for inserting a quote of something from a different Fediverse network?

If I’m on Mastodon and want to quote another Mastodon post it’s (comparatively) straightforward. A new UI control on each post that quotes it instead of replying.

Now suppose I have a Mastodon account and a Lemmy account, both of which are open on a browser on the same device in two different tabs.

I want to quote a Lemmy post in a new Mastodon post.

How does that work?

Is there a new “Compose with quote” button that prompts for the URL of the Lemmy post?

Can I just compose as normal, and paste the Lemmy URL into the compose box, and the server will figure it out? Or is the client supposed to figure it out?

If there is a “Compose with quote” button, what happens if I paste a URL to an item on a server that doesn’t support quoting? Does it fallback to inserting a URL?


Re the “RE:…” text… After followup reading I understand that’s the marker for the quoted post

In the example, why is it at the end of the post? specs-background/quote-posts/quote-posts-research-and-goals.md at 01073ca297dbb5f795976410fdc2bd0edfafcfe6 · mastodon/specs-background · GitHub says you’ll always display quotes first. So shouldn’t it be at the beginning of the text?

If it is in a different part of the post (eg, between two paragraphs) will the Mastodon client honour that? Or will it always show the quote at the start (which risks breaking the meaning of the content).

Or should that bit of the background be amended to say that the Mastodon post editor will always insert quotes first, and clients are expected to render quotes inline at the position of the marker?


@ilja

There is no requirement for servers to implement any specific FEP.

It’s not just servers. Suppose I create a post in a client that doesn’t support creating a quote post, to a server that does

My post is a para, then a link to another post, then another para.

The server SHOULD NOT try and determine if that link is a link to a post on a server that supports quoting, and convert it to a “quote post” link. That’ll change the presentation of the user’s post in a way they don’t expect and can’t predict.

If your client doesn’t support creating a quote post, then you can’t create a quote post. Simply having a link in the content field does not make it a “quote post”; i do not see this implied in this proposal in any way, and it’s even made explicit that a quote is more than (and not even required to be) just a link in the content field.

My comment is not about the quote posts themselves, it’s very explicitly about the controls, expectations, and how to maybe make them more enforcable. Please do not derail.

I don’t believe this is a derail.

Your original comment says:

The biggest problem I see is that current wording make it sound like any server not understanding the FEP is automatically malicious. But if it’s optional, it’s not malicious to not have it.

I am agreeing with that point, and taking it a step further.

A server may implement the FEP, but a client of that server may not support creating quote posts.

So to your quoted comment above I would add the following: “It’s also not malicious for a server to support it, but not use it for outgoing posts.”.

In other words, if you observe a post from a server that you know supports the FEP, and the post links to someone else’s post instead of quoting it, you cannot infer anything about whether or not the server is malicious.

My concern is more about the other way around, but maybe I should make the scenario more explicit;

Let’s say we have A@quote-controls.example.com whose instance implements quote policies and controls as described in this proposal. And we have B@social.example.org who does understand FEP-e232 quote posts, but not the controls and policies as described here.

Yes! That social issue is what I am concerned about. To me, this is basically what the first paragraph of the Security considerations is also about.

I agree that it is quite normal in current fedi, but that doesn’t make it OK. To use the DM example; AP makes an explicit distinction between public or not. Wildebeest leaking dm’s was completely in the wrong of them and people were 100% in their right to tell Wildebeest to fix their implementation. But here we have the opposite scenario. Here the controls are not made explicit by AP. On the contrary even, AP makes it explicit that they are purely optional. As far as I still remember correctly, this is more like how current dm’s were originally implemented in ostatus. (It’s a very long time ago, and I was very new to the network myself, so I could be wrong on things, but from what I still remember how I understood the situation;) They were first implemented in public posts in a similar optional way by adding some property which would be ignored by instances not understanding it, so dm’s started to “leak” through implementations who didn’t understand this optional extension. Contrary to the Wildebeest situation, the problem here was with the sender sending dm’s as basically public posts, not the receiver naively forgetting some check. And the provided fix was to not send these dm’s any more to implementations who didn’t understand it.

Basically what I’m saying is that we can do something about the first security consideration in a similar way;

I agree that A@quote-controls.example.com can’t send the post to B@social.example.org and expect the controls to work. But a trade-off can be considered between wider spread, or more control. More precisely:

  • Wider spread means that A@quote-controls.example.com sends out the note as currently happens, even if it means that B@social.example.org is still fully in the right to quote the post.
    • In this scenario the property "acceptsQuotesFrom": "https://quote-controls.example.com/users/A" basically means “I prefer to not be quoted, but if you don’t understand this property, don’t worry about it, you’re still free to quote post anyhow, I’ll just ignore it myself and others may as well”.
    • Afaict, this is how the current proposal works.
  • More control could be done by only sending to actors who indicate they understand and will enforce these controls properly.
    • In this scenario the property "acceptsQuotesFrom": "https://quote-controls.example.com/users/A" basically means “I do not want you to quote this post, and you have already told me you will respect that wish”.

The second is as strong as dm’s currently work, which could be a worthwhile trade-of to have. Also note that having a way for the second option, does not exclude the first option to still be available for those who prefer that.

Practical

The question for the second scenario then becomes, how to indicate someone understands and will enforce these controls. You send to actors, and when an instance fetches a post, authorized fetch uses an actor as well. So one option is to check the actor. Afaict, the current proposal doesn’t have a specific property set on the actor, but maybe capability discovery can be used. E.g. (I’m using a random fep-888d-like namespace as an example, but another could be used)

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "litepub": "http://litepub.social/ns#",
      "capabilities": "litepub:capabilities",
      "CompliesWithQuoteAuthorization": "https://w3id.org/fep/XXXX#CompliesWithQuoteAuthorization"
      
    }
  ]
  "type": "Person",
  "id": "https://quote-controls.example.com/users/A",
  "capabilities": {
    "CompliesWithQuoteAuthorization": true
  },
}

There may be other, maybe better, ways to do this as well. But this is at least one example how something can be done about the first security concern. It could of course be considered if this is really worth having or not, but yes, at the very least communication should be clear so that no wrong expectations are set if we are to avoid such “social issues” as much as possible .

If you mean “client” in the sense of “Mastodon API client”, then this is the wrong place to have that discussion. The relevant parties here are HTTP user-agents and AS2 publishers/consumers. In the case of a generic server and generic client, the server is basically an agnostic storage and delivery component for arbitrary JSON-LD payloads (perhaps with a few constraints imposed by AS2-Core). The client in this case is occupying the same niche as a Mastodon instance: it generates AS2 activities which the server uses to publish a document/resource and deliver notifications. The document representing the “quote post” is entirely in the purview of whichever entity generates that document (the Mastodon instance). Downstream of the AS2 documents generated by Mastodon, this means that you will have to set those guidelines and expectations for the Mastodon API. For example, the Mastodon API will take a string starting with @ or # or https:// and attempt to interpret and auto-link those. Similarly, Twitter would auto-convert any post ending with a link to a tweet into a “quote tweet” (or auto-convert any post starting with d username into a direct message, for that matter). But these kinds of transformations are out-of-scope here. As far as the protocol level is concerned, content is an opaque string that may be parsed as HTML by default. What that HTML contains is dependent on who generated the AS2 document. So it is nonsensical to say “a server may implement the FEP, but a client of that server may not support creating quote posts.” when the client is the one generating the AS2 document. There is nothing for the server to support about arbitrary and opaque data. The confusion likely stems from “server” being ambiguously used in the same sense as “Mastodon/etc instance” and not “ActivityPub server”. If a Mastodon API client doesn’t support quote posts, then it can’t make quote posts. That’s it. Any and all other concerns should be discussed with the Mastodon project.

It would be up to A’s software at quote-controls.example.org to explain to A that B’s post is “unverified”. quote-controls.example.org might hide or reject or modify B’s post, which means A is not seeing the same thing as what B is seeing, or what C or D might see as outside observers. If C checks for the stamp and finds it missing, it can apply a policy that isn’t necessarily the same as A’s policy; perhaps A strips the quote from the post, C collapses the post under a clickthrough warning, and D drops the post entirely. Everything looks fine to B, but what matters here is how everyone else behaves. In my opinion, the most consistent policy is what A is doing: to ignore the Link tag that makes it a “quote”, but leave the fallback in content. You might also consider parsing the Link tag and stripping the placeholder but refusing to render the embedded preview, but it’s probably better to show a simple link instead of showing a useless box. This stuff is all up to the various implementations, though – all we can do at a protocol level is allow people to verify stamps as an optional step (and then it’s a matter of policy on whether you require stamps or not). The intent is for more servers over time to understand, respect, and validate stamps. If a server doesn’t do this, then there isn’t anything that can be done other than to apply policies like blocking that server. This is up to users and admins to decide on a case-by-case basis.

Yeah, that’s the gist of it. But it still comes down to policy in the end:

which should be the default expectation.

Think of it like this: anyone can link to anything. It’s not a matter of capability. But what you want to prove is that consent was obtained. That’s what the stamp does. I suggested the name “stamp” precisely to draw the analogy to how actual stamps work.

Given this, A should never be shown an unapproved quote in the same way as an approved quote. Using the same UI for both is what would cause the confusion.

Trying to negotiate it as a capability is more trouble than it’s worth at this stage. But I do think some kind of protocol negotiation would be useful to have as a more general framework. This kind of effort is something I proposed as a SocialCG task force back in November, but it never got resolved because the focus right now is on charter stuff.