Desired changes for a future revision of ActivityPub and ActivityStreams

But you’d still need a domain no? Every user can’t be expected to have an additional domain. Probably even choosing an extra domain from a list of options would be too confusing.

This is a lot of assumptions. I don’t see why we would expect identity.example to be more stable than the others and requiring these extensions is a big ask.

But the core of the problem is that any solution to this problem would be entirely optional and not required by the spec, which makes it very unreliable and not really a solution at all.

You don’t need a domain for every user though??? You just need an identity. You have the option of using an identity on someone else’s domain, or on your own if you want to set one up. Just like mastodon.social provides me an identity https://mastodon.social/users/trwnh right now, except Mastodon doesn’t currently support pluggable identities, so I can’t point trwnh.com at mastodon.social in any meaningful way.

As to what is “required by the spec”, there are multiple specs. ActivityPub leaves out exact mechanisms of storage and identity and authentication and authorization and several other things that are out-of-scope for ActivityPub. It provides a signalling and delivery mechanism, as well as limited side effects in case of things like Follow/Like/Announce. These “missing bits” don’t belong in the ActivityPub spec, they belong in a “protocol” that includes more than just ActivityPub.

As to assumptions: if your identity provider is shorter-lived than your storage provider then something is wrong on a deeper, fundamental level that ActivityPub will not and cannot ever address. At least right now, they are the same lifespan, because they are the same domain. If they were separate, you would have to choose an identity provider that you trusted to stay up for longer than the time that you are using a given storage server. (Not how long the storage server remains up, but how long you personally are going to continue using it.) The bet, wager, gamble, assumption, etc. you are making is that something like purl.org will outlive your files being on a given domain. Right now, https://w3id.org/fep points to https://codeberg.org/fediverse/fep because the FEP repo is hosted on Codeberg. But if for some reason it later moved to a selfhosted Forgejo instance running on some other domain, or to a static website, all current links pointing to Codeberg would immediately rot. But after I or someone else updates the w3id.org config, all links pointing to w3id.org would continue to resolve just fine. You’re not making a lot of assumptions (2) or requiring too many extensions (1) to say that ActivityPub should maybe formalize the concept of “the actor’s namespace”, so that it can be a variable instead of hardcoded to whatever the server assigns you as the actor id. (And some servers don’t even do this – they might assign objects and activities in the general namespace of the domain, e.g. everything might be stuffed in an /objects subdirectory as /objects/id.)

1 Like

I would like to mention a bullet list by @hrefna, cross-referenced with permisssion …

In general when I think “things I want to see in AP2” what I end up with are things like:

  • Protocol negotiation.
  • Removing inbox forwarding
  • Requiring secure transport
  • Reply management
  • Reply fetching improvements
  • Adding a concept of a “server” and providing the ability to send batched messages between them.
  • OpenAPI and/or JSON Schema compatibility.
  • Making types work in a way that is intuitive if you don’t have a RDF background.
  • Moderation improvements.
  • Algorithm documentation
3 Likes

I think the rest looks good (if a bit vague, obviously it’s not explained in detail) but I’m still confused about this suggestion - I get that forwarding could lead to circumvention of blocks (are there any other problems?), but it also has real use cases.

For example, if you can’t forward messages, how would you forward a reply to the followers of the one you’re replying to? I.e. A posts something. B replies with another post. B’s post should be sent to A’s followers so they also see the reply, but B can’t do that directly, since B doesn’t know A’s followers. So B sends the reply to A and A forwards the reply to their followers. How can you do this if there is no forwarding mechanism?

But from the get-go, I don’t really understand how you can not support forwarding, unless you have some form of end-to-end-encryption (and I don’t understand how that could possibly work for a public message, and even then E2EE is incredibly complicated and hard to get right, see e.g. Matrix).

@jenniferplusplus had a great writeup for this as well! I’ll let her repost it herself.

1 Like

You’re welcome to share whatever I’ve written on the topic, but this thread isn’t the place for me.

4 Likes

I think this is what I’d call “client-to-client protocol”, and I definitely agree that it should be signalled somehow. We might end up in a situation where you have to signal clients attached to your actor anyway, because E2EE requires client-to-client anyway.

This is possible with fep/fep/7458/fep-7458.md at main - fediverse/fep - Codeberg.org but I’d also caution against giving replies such preferential treatment. It makes more sense to logically and purposefully group things by their context as described in fep/fep/7888/fep-7888.md at main - fediverse/fep - Codeberg.org – which MAY be a flat list OrderedCollection as a progressive enhancement, owned by some moderator or manager who explicitly maintains the context. (This is in the case that it does resolve, does resolve to a collection, and does have an owner.) But replies can definitely exist in parallel to that, so you have the option of maintaining a list of responses to your object, as well as declaring that your object is part of some larger context.

This is one of the things I disagree with, and rather I think that inbox forwarding should actually be further specified.

We don’t need the concept of a “server”, do we? We just need an endpoint that allows for batch delivery, which may or may not be server-wide. That’s an implementation detail that gets abstracted away once you start dealing with things in terms of “inboxes” and the corresponding inbox endpoint. It still exists in a C2S perspective because the “server” is the HTTP server that is processing your activities. This could also tie in with a point above about having the concept of a “client” at the protocol level.

For one potential way to do batch delivery, there was some discussion around FEP-0499 and multibox noting that it could be used for batch delivery:

More discussion here: FEP-0499: Delivering to multiple inboxes with a multibox endpoint - #3 by silverpill

Definitely! There’s a lot of pain points around how implementations have dealt with types up to this point, which is to say, poorly or not at all. (Every time I see an explicit type check that doesn’t have a reason, I die a little on the inside.)

I think types should signal information that can’t be otherwise signaled by a property. For example, the difference between a Note and an Article is mostly semantic and depends on whether the text is “formally published” or not. They can otherwise have the same properties. Distinguishing between a Note and an Article is a good use of type. But other things can be generally duck-typed or you can just use a property if it’s present without even bothering to check the explicit type. For example, do you really care that something is an as:Collection if you can just check for as:items? What’s really necessary here to provide the functionality you’re trying to provide? The type check does nothing except make your code more fragile. I think part of the problem is implementers assuming type is necessary when it really isn’t. But this is also the kind of thing that can be addressed with more thorough guidance. Speaking of guidance,

This is a really good thing to have and it’s sad that we don’t have it currently.

2 Likes

Thanks @trwnh! It is a pity all the quotes look like coming from me, but it is @hrefna’s. Maybe you can edit, make them markdown quotes?

Sure thing:

@jenniferplusplus said:

I guess I can tell you what I would want:

  1. Explicit, implementable, testable requirements.
    1.5. A test suite, for that matter.
  2. Schemas and structure that are defined well enough to support code generation.
  3. A built-in, specification-defined, federated authentication scheme.
  4. Separation between the envelope and the message.
  5. A flat and efficient message structure.
  6. A design explicitly oriented around delivering messages, as opposed to synchronizing graphs. In support of…
  7. Messages with limited access. Meaning, open to a group, but not the public and also not requiring explicitly enumerated individuals. I recognize this means confidentiality cannot be guaranteed, and I’m fine if this requires good faith peers. I would like if breaches of confidentiality are at least detectable.
  8. Clear rules governing interaction with other people’s objects, with an enforcement mechanism.
    8.5. Clear rules governing shared ownership/control of objects, with a well defined coordination mechanism.
  9. A well defined mechanism to converge local states with the owner’s defined state. This likely flows easily from no 8.
  10. A well defined extension mechanism, with generally similar requirements for extensions.
  11. An extension registry.
  12. Negotiable, but I’d really like to have structured identifiers, to support determining what an object is and what it’s relationship is to other objects without having to build and examine a complete object graph, first.
  13. Negotiable, but I think a separation between persistent, contentful messages vs ephemeral presence updates would go a very long way toward reliable and predictable interoperability.
  14. A concept and representation of a server, or something like it. A data custodian, or handler, or similar.

And things I do not care about, because they come up a lot:

  1. Nomadic and/or sovereign identity
  2. Data sovereignty
  3. E2EE
3 Likes

Just reading a Vice article about the history of RSS, and this quote reminded me of some of the debates here about JSON vs. JSON-LD, and about AP as an extensible core (a la XMPP) vs. AP as a kitchen sink protocol for all possible social web uses (a la Matrix);

“In the same post, he argued that RSS had succeeded so far because it was simple, and that some of the changes being proposed in the Syndication mailing list would only make RSS “vastly more complex, and IMHO, at the content provider level, would buy us almost nothing for the added complexity.” He was especially set against any plan that would add namespaces to RSS or reintroduce the RDF formalisms that had been dropped before the release of RSS 0.91. (Namespaces would basically allow programmers to define sub-formats of RSS, meaning that cool new functionality could be added to RSS without everyone agreeing on every detail. But namespaces would make writing software to read RSS more challenging.)”

I have a POV to this. TL;DR: JsonLD is not the optimal solution to ActivityPub’s problem.

Why? Because parsing it is hell. It essentially restricts ActivityPub implementations to only use the most popular languages: Java, Ruby, JavaScript, Go, and that’s about it.

Yes, in theory, you could implement JsonLD yourself. But I tried, and failed. In fact, I didn’t even try implementing it entirely; I used Kotlin/Multiplatform, which meant for every platform, I could interop with a native language to handle expansion and compactions. All I needed to do was implement deserializing expanded JsonLD to Kotlin objects - and as for serialization, I could just compact the output. But it was still incredibly, incredibly time-consuming and hard.

The amount of effort I invested in falling to parse JsonLd was a lot, a lot higher than the amount one needs to invest to implement everything else, including a module system, Database connection, and proper DAO/DTO separation - I’m around 2 or 3 days around from finishing implementing Webfinger, and I’ve invested around 2 days so far (and I don’t even have prior knowledge of SQL!).

Considering that parsing JsonLD is practically not happening, the only benefit it brings us is a guarantee that collisions can’t happen. I.e. if a standard defines a type, it cannot be confused with an identically named, yet different, meaning (note, nothing can solve the absolute lack of definition of most built-in ActivityPub types). We don’t need and de facto don’t have any other benefit JsonLD may or may not provide.

Before we choose a brand new solution to “replace” JsonLD, we also need to consider that de facto all ActivityPub implementations use SQL databases, not Json databases; as a side-effect of this fact, Json fields that are not understood are simply dropped, and provide no extra informational value; that is, support for fields (and types) has to be explicitly implemented by the instance; therefore, expanding JsonLD to find out that Something stands for example.social/context.jsonld#Something during communication is simply a waste of resources; it is much more efficient to decide on this beforehand, as a part of a first contact protocol as I’ve previously suggested.

Now, JsonLD doesn’t actually have any negatives; it just doesn’t provide the positives it was originally used for. There’s no need to drop it.

The optimal solution, that allows both the power of JsonLD to those who need it, and fast implementation for those who don’t, is an extension of sorts to JsonLD to negotiate the used contexts beforehand. This is not, though it could be a part of, the same thing as a feature/ActivityPub extension support discovery mechanism; it is simply a hypersimplification of JsonLD parsing: we aren’t expanding and compacting anymore, we’re just using different names for the same properties with different servers (or alternatively, refusing to interact with anyone disagreeing with us).

But besides, the actual interopability problem rests in ActivityPub simply not standardizing enough. When it is up to implementation of a social network to decide what a post is, the standard is flawed. I personally believe in a small AP core and FEPs to extend it, simply because of the flexibility.

1 Like

I’m working on an FEP for this, 5a4f federated democracy. It’s not merged into the repository yet, as I need to implement it first.

The problem here is with AS2, not with JSON-LD. The majority of AS2 properties can be a string, a reference, an object, or an array of any of these. AS2 forces everyone to handle every possibility, instead of being explicit and serializing objects as actual JSON objects.

Consider the following scenario where you want to display someone’s avatar – a pretty basic task. Here’s what you have to deal with:

{
  "icon": "https://something.example/icon"  // is this a Link node, or an Image object, or a URI to the resource?
}
{
  "icon": {
    "type": "Link",
    "name": "Avatar"
    "href": "https://something.example/icon",
    "width": 400,
    "height": 400,
    "mediaType": "image/png"
  }
}
{
  "icon": [
    {
      "type": "Link",
      "name": "Avatar, full size"
      "href": "https://something.example/icon",
      "width": 400,
      "height": 400,
      "mediaType": "image/png"
    },
    {
      "type": "Link",
      "name": "Avatar, profile size"
      "href": "https://something.example/icon?resize=120",
      "width": 120,
      "height": 120,
      "mediaType": "image/png"
    },
    {
      "type": "Link",
      "name": "Avatar, timeline size"
      "href": "https://something.example/icon?resize=48",
      "width": 48,
      "height": 48,
      "mediaType": "image/png"
    }
  ]
}
{
  "icon": {
    "id": "https://something.example/icon"
    "type": "Image",
    "name": "My avatar",
    "summary": "Image description goes here",
    "url": "https://something.example/icon.png"
  }
}
{
  "icon": {
    "id": "https://something.example/icon"
    "type": "Image",
    "name": "My avatar",
    "summary": "Image description goes here",
    "url": [
      "https://something.example/some-uri",  // is this a direct resource URI or a Link?
      {
        "href": "https://something.example/icon.jpg"  // no mediaType, so you have to guess based on file extension
      },
      {
        "href": "https://something.example/icon.png"  // no mediaType, so you have to guess based on file extension
      },
      {
        "href": "https://something.example/icon.avif"  // no mediaType, so you have to guess based on file extension
      },
      {
        "href": "https://something.example/icon.jxl"  // no mediaType, so you have to guess based on file extension
      }
    ]
  }
}

All of these are generally valid by AS2. So the consumer now has to be able to parse multiple representations instead of just one. And even after parsing, you still have to figure out what to do with the information that you parsed!

If anything, JSON-LD can simplify things, by allowing you to effectively “normalize” the data beforehand, so that you have less possibilities to deal with. Everything becomes an array of JSON objects. You still have problems because of AS2 (for example, knowing if any given @id is a resource, Link, or Image), but you eliminate a lot of potential cases. Your logic doesn’t need to handle bare strings or singular objects anymore; you only need to loop over the array and pluck whichever JSON object matches your criteria. Which, again, your complications beyond this point are the fault of AS2.

No, it’s not, and it shows you haven’t read my post properly.

ActivityStreams is entirely correct in considering all of these structures as valid, as it assumes you’re expanding your JsonLD before reading it. You can’t both say “JsonLD is a perfect fit” and “the imperfections are in ActivityStreams, because it doesn’t patch JsonLD to make it fit”.

Just to have said my 2 cents:

I do not believe the ActivityPub, ActivityStreams specifications should be continued.

One should start over and focus on behavior instead of data formats, so the result is testable.

3 Likes

AFAIK, this is not true. I used to think of AS2 as being JSON-LD but after extensive discussions with Evan I realize it’s something else. What? I’m not exactly sure. And I don’t have any reason to believe that anyone else knows either.

Section 1.2 of the AS2 Core spec describes one of the motivations to be:

Compatibility with JSON-LD

In other words, AS2 is compatible with, but not actually, JSON-LD. From AS2 Core Section 2 (emphasis mine):

This specification describes a JSON-based [RFC7159] serialization syntax for the Activity Vocabulary that conforms to a subset of [JSON-LD] syntax constraints but does not require JSON-LD processing.

It goes on to say in AS2 Core Section 2.1:

The serialized JSON form of an Activity Streams 2.0 document MUST be consistent with what would be produced by the standard JSON-LD 1.0 Processing Algorithms and API [JSON-LD-API] Compaction Algorithm using, at least, the normative JSON-LD @context definition provided here.

The “with what would be produced” phrase is significant. No one is required or even expected to actually use JSON-LD processing algorithms (like expansion, or even compaction) with AS2 JSON. Of course, one of the few claimed benefits of JSON-LD (extensions) is lost without any JSON-LD processing, but I think that’s just one aspect of the AS2/AP madness.

AFAICT, historically, JSON-LD was used to define AS2 prior to the W3C WG. Since JSON-LD is an RDF serialization (per the JSON-LD 1.1 spec) this triggered discussions about the RDF modeling of AS2 data in the early WG discussions. RDF was eventually rejected, but it seems like the JSON-LD “consistency” was kept because it was a good political fit with the W3C focus on Linked Data. In the meantime, the Linked Data / RDF folks split off from the WG to work on the Solid project.

In a sense, you’re logically correct if AS2 is not JSON-LD. But then you wrote that “JSON-LD can simplify things”, which makes me wonder if you still think it is? If so, I believe the AS2/JSON-LD issues are deeply entangled and significantly damage the clarity of the Recommendations. However, I still claim AS2 was/is not intended to be JSON-LD per se. If it were, it would then literally be an RDF data model serialization. Evan has made it very clear that it is not that and therefore not actually JSON-LD.


So, AS2 is just plain JSON that is “compatible” or “consistent” with a subset of JSON-LD. I know there are many counterarguments (I’ve made many myself, to no avail), but until the “owners” of the AP/AS2 Recommendations understand and acknowledge the insanity or that there’s even an issue here, nothing is going to change.

O, that way madness lies; let me shun that;
No more of that. – Shakespeare’s King Lear

:rofl:

4 Likes

It’s not strictly JSON-LD if you don’t treat it as such. Part of JSON-LD, by my understanding, is a way to “upgrade” JSON serializations into something unambiguous. You can write your own context or otherwise use one provided for you, without it necessarily being declared in the document via @context keyword. (Because of this, I’ve previously said things like “JSON is just JSON-LD without the context / with an implicit context”.)

Put another way, the context mapping is a way to reshape any arbitrary JSON, no matter how ambiguous or indirect (with few exceptions – you might need separate terms like “items” vs “orderedItems”, and worst-case you can just use @type: @json from 1.1) into that unambiguous form you probably want. But because of the “MUST be consistent with what would be produced” bit you pointed out, you end up needing to be ambiguous and indirect. This in effect forces AS2 to be “bad JSON”. Expanding AS2 into expanded form JSON-LD might fit things into a certain shape (that I maintain is slightly easier to work with, due to it reducing the problem space), but it does nothing to fix the inherent wrongness of AS2, especially for the values of AS2 properties. This is what I mean when I say JSON-LD is not the problem, AS2 is.

The vocabulary of AS2 is mostly fine, it’s the data model that is at best “insufficiently constrained”. There’s ways to make it less bad, but beyond a certain point it requires certain constraints to be added in a mostly backwards-incompatible way, to varying degrees. If you go by the “with what would be produced” definition, then I believe the following changes to the normative context would make things conceptually clearer, if more verbose:

  • For every non-functional property, explicitly add @container: @set. This forces everything to be an array. No more dealing with values that could be single objects or arrays; they would now be always arrays. (This has the additional benefit of reducing the likelihood of implementers incorrectly assuming that certain properties can only have one value, when in fact they can have multiple values.)
  • For every property that is defined as @type: @id, drop this from the definition. This forces every reference into its object form. No more dealing with strings that may or may not be expandable into graph nodes; they would now always be represented as graph nodes.

This turns

"inReplyTo": "https://something.example"

into

"inReplyTo": [{"id": "https://something.example"}]
1 Like

If it isn’t true, then AS2 isn’t a standard, de facto: a collection of MAYs isn’t a standard, it’s guidelines of how you want one to look like. Now, regardless of this ideological declaration, it is true in how ActivityStreams was designed.

I.e. read that statement as “assuming JsonLD is used, which is how AS was designed, it makes perfect sense”.

This entire thing goes into “ActivityPub is a political standard, not a technical one”, because every part of it was designed by different people trying to push their own agenda.

Now, I don’t think it actually matters what standard’s fault it is that AP sucks; it just sucks, and a vNext (or more realistically, a protocolNext), is needed.

(I know I’m not differentiating properly between ActivityPub and ActivityStreams 2.0; tbh, I don’t think there’s an actual difference between them, de facto; I personally haven’t seen non-AP usage of AS 2.0)

Anyhow, back on topic. Here’re the ‘desired changes’ I’ve seen so far, and my comments on each.

  • Test suite—strictly mandatory.
  • Extension mechanism beyond @context—Generally, fully move into “JsonLD-compatible Json protocol”, which means we no longer rely on “but JsonLD gives us X”, but instead implement X ourselves in a JsonLD-compatible way. That is, fully accept we just don’t parse JsonLD (same thing I talked about in my post except differently worded)
  • Require compacted data transfer and schemas.
  • Definition of what a server is and what it is responsible of—Not that it should be changed, it’s just unclear if we’re doing P2P, server/client, internet gods/random Mac addresses, or whatever else, and we keep arguing about this.
  • Generally unambiguoufy things.

I’ve intentionally excluded complete ideological reforms (ActivityPub does what it does badly vs what ActivityPub is doing is bad), as they’re essentially different protocols, and I don’t believe they’re strictly needed—but all of the changes I’ve mentioned’d apply to such a reform as well.

(I guess I’ve just countered myself. Ideological reforms vs not is the “is ActivityStreams the problem or is it JsonLD” discussion, except more well-put; I guess this puts me in the “JsonLD is the problem” camp, although I disagree it is a problem as of itself. Wait, this makes me a part of the ActivityStreams is the problem camp, because I believe AS should’ve defined its normal Json functionality better? Idk, I’ve lost track of this conversation by now)

2 Likes