Negotiating protocols between actors or clients

for prior art within the xmpp ecosystem, it looks like XEP-0115: Entity Capabilities is the nearest analogue for this kind of thing? i guess also XEP-0030: Service Discovery can be used, and XEP-0390: Entity Capabilities 2.0 looks like an overhaul that got deferred.

You both keep talking about the ā€œMastodon protocolā€. So I have to repeat it once again: Mastodon is a software. It is not a protocol.

ActivityPub specification already says it in section 5.2 Inbox: ā€œThe inbox stream contains all activities received by the actor.ā€. Why it should be a profile?

This also seems to be described in the spec.

Exactly. Either you ask implementers to copy Mastodon behavior bug-by-bug, or you create a profile by arbitrarily picking micro-blogging features. The latter might be useful, but then it is not clear why you insist on calling it ā€œMastodon profileā€ and not ā€œmicro-blogging profileā€.

This is the level on which this work should be done. Emoji reaction is a singular feature, it can’t be broken down into smaller parts. Why it is a profile though?

FEP-400e, section ā€œSpecifying collections in actorsā€:

Implementations MAY use the presence or absence of specific collection to determine whether the actor’s server supports features that depend on that collection and alter their UIs accordingly.

This idea can be generalized further, to using other kinds of properties for feature detection.

Call it the M Protocol, that Mastodon and many other software implementations use to interoperate, if that naming makes it easier for you to separate protocol from implementation of the protocol.

2 Likes

how a ā€œnotification profileā€ differs from 5.2 Inbox

a ā€œnotification profileā€ or ā€œminimal profileā€ would signal that there should be no side effects; the AP message represents something that happened in the past, not a command to do something. for example, if you receive a Create notification, then do not attempt to cache the object, or convert it to a status, or anything else – once you add the notification to the inbox, it’s a no-op.

GET /resource HTTP/1.1
Host: remote.example
Accept: application/activity+json

{
  "id": "https://remote.example/resource"
  "inbox": "https://remote.example/notifications",
  "summary": "This is a resource with an ldp:inbox that accepts Linked Data Notifications. It does NOT implement any side effects described in ActivityPub or in any other specification."
}
OPTIONS /notifications HTTP/1.1
Host: remote.example

HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS, POST
Accept-Post: application/ld+json
Accept-Post: text/turtle
Accept-Post: application/n-triples
Accept-Post: application/activity+json; profile="https://w3id.org/fep/xxxx/profile/notification"
POST /inbox HTTP/1.1
Host: remote.example
Content-Type: application/activity+json; profile="https://w3id.org/fep/xxxx/profile/notification"
Link: <https://w3id.org/fep/xxxx/profile/notification>; rel=profile

{
  "actor": {
    "type": "Person",
    "name": "a",
    "url": "https://trwnh.com"
  },
  "type": "Create",
  "object": {
    "type": "Note",
    "url": "https://trwnh.com/notes/1"
  },
  "to": "https://remote.example/resource",
  "summary": "a created a note. (this is intended as a passive notification that has no other side effects.)"
}

HTTP/1.1 200 OK

the ā€œresource managementā€ or ā€œremote c2sā€ profile

it is not.

you may be thinking of what happens when an Activity is posted to an outbox, not what happens when an activity is posted to an inbox. the activitypub spec has two separate sections for c2s activities and s2s activities.

for example, c2s Create says this:

The Create activity is used when posting a new object. This has the side effect that the object embedded within the Activity (in the object property) is created.

When a Create activity is posted, the actor of the activity SHOULD be copied onto the object’s attributedTo field.

A mismatch between addressing of the Create activity and its object is likely to lead to confusion. As such, a server SHOULD copy any recipients of the Create activity to its object upon initial distribution, and likewise with copying recipients from the object to the wrapping Create activity. Note that it is acceptable for the object’s addressing to be changed later without changing the Create’s addressing (for example via an Update activity).

but s2s Create says this:

Receiving a Create activity in an inbox has surprisingly few side effects; the activity should appear in the actor’s inbox and it is likely that the server will want to locally store a representation of this activity and its accompanying object. However, this mostly happens in general with processing activities delivered to an inbox anyway.

notice that the s2s Create does not create the object.

so when i perform the following POST to an inbox (not an outbox!), by default, the object will not be assigned an identifier; the payload will not be mutated in any way; no further processing actions occur.

if we define a profile for resource mangement, then it becomes semantically clear that the intent of the activity is to manage resources and not to serve as a bare notification.

GET / HTTP/1.1
Host: storage.example
Accept: application/activity+json

{
  "id": "https://storage.example"
  "inbox": "https://storage.example/inbox",
  "summary": "This is a storage service with an ldp:inbox that accepts ActivityPub messages with Create/Update/Delete/Add/Remove, but applies the C2S side effects instead of the S2S side effects."
}
OPTIONS /inbox HTTP/1.1
Host: remote.example

HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS, POST
Accept-Post: application/activity+json; profile="https://w3id.org/fep/xxxx/profile/resourceManagement"
POST /inbox HTTP/1.1
Host: storage.example
Content-Type: application/activity+json; profile="https://w3id.org/fep/xxxx/profile/resourceManagement"
Link: <https://w3id.org/fep/xxxx/profile/resourceManagement>; rel=profile

{
  "id": "https://trwnh.com/some-activity",
  "actor": {
    "id": "https://trwnh.com/actors/me/index.as2.json",
    "type": "Person",
    "name": "a",
    "url": "https://trwnh.com"
  },
  "type": "Create",
  "object": {
    "type": "Note",
    "url": "https://trwnh.com/notes/1"
  },
  "to": "https://storage.example/inbox",
  "summary": "a created a note. (this is intended to also store the Note and the Create on storage.example)"
}

HTTP/1.1 201 Created
Location: https://storage.example/07cb49a5dca1ef2b

GET /07cb49a5dca1ef2b HTTP/1.1
Host: storage.example

HTTP/1.1 200 OK
Content-Type: application/activity+json

{
  "id": "https://storage.example/07cb49a5dca1ef2b",
  "alsoKnownAs": "https://trwnh.com/some-activity",
  "actor": {
    "id": "https://trwnh.com/actors/me/index.as2.json",
    "type": "Person",
    "name": "a",
    "url": "https://trwnh.com"
  },
  "type": "Create",
  "object": {
    "id": "https://storage.example/c7304d4a5fabe02c",
    "type": "Note",
    "url": "https://trwnh.com/notes/1"
  },
  "to": "https://storage.example/inbox",
  "summary": "a created a note. (this is intended to also store the Note and the Create on storage.example)"
}

why profiles at all

instead of asking implementers to copy Mastodon bug-for-bug, you don’t ā€œarbitrarily [pick] micro-blogging featuresā€, you find a common baseline that describes the behaviors encompassed by the profile. for example:

  • Create creates a Status if and only if:
    • actor is present
    • type is present
    • object is present
    • published is present
    • type equals or includes Create
    • actor.id == object.attributedTo.id
    • object.type == Note
    • published is within a 12 hour window of the current instance server time
    • …
    • …
    • …
    • [the exact set of requirements to get Mastodon/etc to not drop your activity on the floor is an exercise left to the reader. this varies depending on who you target for compatibility.]

you might call this a ā€œfeatureā€ for authoring a post. i call it a ā€œprofileā€ because it has to do with semantic processing of the message, not how it gets represented in the UX at the end. as demonstrated above, there are multiple ways to interpret and process a Create, and the purpose of the profile is to modify the semantics and processing rules. the end result of the message is a behavior – ā€œif I send a Create of a certain shape, it will be interpreted in a specific way and have specific side effects.ā€ the behavior here is generating a status and attaching it to an account, which is not the same as creating an object and attributing it to an actor. Statuses are a specific entity; they are specified by Mastodon’s documentation. Likewise for Accounts. There are differences between Statuses and Objects, and there are differences between Accounts and actors. the work being proposed and described here is to identify those differences, and then identify how broadly across the ā€œfediverseā€ they can be applied.

the ā€œfeatureā€ of an ā€œemoji reactā€ can be considered a ā€œprofileā€ of the Like activity. this is because it both extends and restricts the semantics of a Like activity. for example, the basic interpretation of a Like activity in the ā€œMastodon profileā€ is that content gets ignored. if you instead apply the ā€œemoji react profileā€ to the Like activity, you are allowed to include content, BUT that content MUST be a Unicode emoji or otherwise it MUST be the name of a toot:Emoji in tag. so for the following activity:

{
  "actor": "https://someone.example",
  "type": "Like",
  "object": "https://local.example/some-object",
  "content": "That was an excellent post! I very much enjoyed it, and I enjoy your writings in general.",
  "to": "https://local.example/someone"
}

by the ā€œmastodon profileā€, you have the following behaviors:

  • it gets converted to a ā€œlikeā€ entity where the like is from the Account https://someone.example for the Status https://local.example/some-object
    • however, if https://someone.example does not fit the requirements for an Account, the entire activity will be dropped.
    • if https://local.example/some-object is not local, the entire activity will be dropped.
    • despite being addressed only to https://local.example/someone, it will in fact be shown publicly to anyone who loads the Status’s list of likes.
    • content will of course be ignored.

but the activity is generally considered ā€œvalidā€ by the ā€œmastodon profileā€, because it fulfills the requirements.

meanwhile, if you try to process it according to the ā€œemoji react profileā€, this Like activity would actually be invalid and might be dropped. or otherwise it would fallback or degrade to a ā€œmastodon profileā€ Like activity. or perhaps something else entirely. the point is that you have no way of knowing before sending the activity, unless you profile every single software in the fediverse. what do you expect to happen when you send someone a Like? do they even support Likes? which properties are required? what must or must not their values contain? there are a lot of unanswered questions.

1 Like

right, i should clarify that when i say ā€œmastodon profileā€ i mean ideally what is described in ActivityPub - Mastodon documentation which despite being titled ā€œactivitypubā€ is in fact a set of additional requirements on top of activitypub — and i use this as an example simply because it’s one of the better-documented list of extra requirements.

I neglected to mention this before, but in Linked Data Notifications there is a link relation of http://www.w3.org/ns/ldp#constrainedBy that seemingly could help here:

Inbox URLs can announce their own constraints (e.g., SHACL, Web Annotation Protocol) via an HTTP Link header or body of the resource with a rel value of http://www.w3.org/ns/ldp#constrainedBy. Senders should comply with constraint specifications or the receiver may reject their notification and return an appropriate 4xx error code.

This is mostly in line with some earlier discussion in FEP-eb22: Supported ActivityStreams types with NodeInfo - #11 by trwnh …but unfortunately, the LDN spec didn’t flesh this out fully. It’s not explained how a constraint should be described. Web Annotation Protocol describes basically using what amounts to an opaque profile URL:

Link: <http://www.w3.org/TR/annotation-protocol/>; rel="http://www.w3.org/ns/ldp#constrainedBy"

Presumably the simplest theoretical analogue would be something like:

Link: <http://www.w3.org/TR/activitypub/>; rel="http://www.w3.org/ns/ldp#constrainedBy"

Or possibly even:

Link: <https://docs.joinmastodon.org/spec/activitypub/>; rel="http://www.w3.org/ns/ldp#constrainedBy"

This is perhaps not ideal, because:

  • it relies on a lot of out-of-band knowledge, so it isn’t very useful for validating your payloads ahead-of-time (as SHACL would allow you to do).
  • You also end up with a potential proliferation of these ā€œconstraint URIsā€ which is not so much a problem for humans as it is a problem for machines.
    • Constraints may vary over time and thus should be versioned. This means even more URIs to deal with.
  • There is no inherent decomposition possible into separate features, since there is no expectation that the link resolves to anything machine-usable.
  • The scope of this link relation is more closely tied to validating payloads than it is describing side effects or behaviors.

…but at the very least, it’s worth bringing it up as prior art. Solid Protocol alludes to this briefly but still doesn’t define it.