Desired changes for a future revision of ActivityPub and ActivityStreams

That makes sense. One concern with such a thing is that it sounds like you’d need people to choose both their instance and their key holder. And it’s already a major hurdle for people to understand how/why they need to choose an instance. But perhaps this is a usability gap that can be bridged.

right, so, as i keep saying, you can do this

but doing this is like 80-90% of the way to using json-ld expanded form, the only thing missing at that point is requiring all property values to be normalized to sets of id-nodes or value-nodes.

so you turn this

{
  "@context": {
    "discoverable": "http://joinmastodon.org/ns#discoverable"
  },
  "discoverable": true
}

into this

[{
  "http://joinmastodon.org/ns#discoverable": [{"@value": true}]
}]

that’s literally json-ld expansion.

if instead you want something more like this:

{
  "http://joinmastodon.org/ns#discoverable": true
}

then that’s fine as well, but it leads to ambiguity when you have something that could either be a @value of the literal URI as a string, or an @id that points to another node on the graph (i.e. something you can fetch and embed, or do other things with)

so for example

{
  "http://joinmastodon.org/ns#featured": "https://mastodon.social/users/trwnh/featured"
}

how do you know whether https://mastodon.social/users/trwnh/featured is its own object, or if it’s literally the string/uri value “https://mastodon.social/users/trwnh/featured” instead?

json-ld compacted does this with @type: @id in the @context. in json-ld expanded form you would do it by explicitly declaring your node to be an @id node:

[{
  "http://joinmastodon.org/ns#featured": [{"@id": "https://mastodon.social/users/trwnh/featured"}]
}]

what do you propose to disambiguate {"@id": "https://mastodon.social/users/trwnh/featured"} from {"@value": "https://mastodon.social/users/trwnh/featured"} in “plain JSON”


there is a second, finer issue here with this bit

this is, again, going back to the open-world vs closed-world thing. because in a closed-world you can just say, sure, there is only discoverable, or mastodonDiscoverable or discoverableByMastodon or http://joinmastodon.org/ns#discoverable or whatever. pick one and stick to it. but you enforce that every single member of your “closed world” shares this same understanding, and that they only ever use the one symbol that maps exactly to the concept.

what i am saying is that it is useful to be able to identify equivalence between symbols. especially in an open world, you require this. because people are not going to use the same symbols, and they are not going to agree on exactly one symbol to represent a given concept. there is not a 1:1 mapping of symbols to concepts.

consider the english language, or whatever language you want to consider. there are shorthand words, and then there is their full definition, and there is not a 1:1 mapping between words and definitions. there are also synonyms, which are different words that represent the same concept. equivalence is built into language at a fundamental level. i can say Bill or i can say William Jefferson Clinton or i can say the 42nd president of the USA and you might understand that to mean the same individual (owl:sameAs). what we can do for individuals, we can do more generally for concepts. e is the same as the fifth letter of the english language or that fifth glyph. we can also say did:web:trwnh.com and https://trwnh.com/.well-known/did.json are different identifiers for the same resource (as:alsoKnownAs). we can say that Person as defined in AS2 and Individual as defined in VCard represent the same type of thing (owl:equivalentClass). we can say that name as defined in AS2 and name as defined by schema.org represent the same thing (owl:equivalentProperty). all these equivalences are useful. and more importantly, they can be codified in a format that is both machine-readable and human-readable, that can be used to infer things logically without being told beforehand. i can tell you my as:name is "a" and then tell you schema:name is an equivalent property to as:name, and you can infer logically that my schema:name is "a" without me explicitly stating it.

to throw away the very concept of shorthand terms and contextual understanding in favor of a strict 1:1 mapping (probably in some registry somewhere) is, at the very least, extremely limiting. and maybe you think being extremely limited is a good thing for the fediverse – after all, limits can constrain the problem space. you don’t need to be aware of multiple terms for the same thing, or handle the cases where the same term means different things. this is up to the “protocol”.

and this is why i said AP is not enough as a “protocol”, because the fediverse does a lot more that isn’t even in the realm of AP as a spec. you need other specs. you need some form of extensions. it’s just that you have to choose one of the following:

  • strictly limit extensions, maintaining some mapping of terms to concepts that ensures 1:1 relation (one term always represents one concept, and one concept is always represented by one term)
  • let everyone extend freely, using whatever terms they want (possibly using multiple terms for the same concept or using the same term for multiple concepts)

Since Solid (social linked data) is also going to a W3C Working Group I think it’s going to be a good idea to have the two aligned to a degree. I’ll be doing some work there, I think. It’s actually not all that hard to align JSON(-LD) and RDF – not trivial – but you basically can make a sensible JSON-LD profile that’s reasonably easy to parse in JSON and that works with RDF. And you need to take care on the document / Thing separation which was partially done first time round, but not fully. This is a small back compatible change.

1 Like

This could well be a very good idea!

I would know because I know the meaning of http://joinmastodon.org/ns#featured. So that key would define what its value means. There is no @id or @value or @context stuff in this plain JSON land. You just have keys and values and keys have defined meanings. Maybe the definition of http://joinmastodon.org/ns#featured says that its value is to be interpreted as a URL that could be fetched to get some object. Maybe it would just say that its value is a boolean. That’s up to whoever defined the term. Anyone can define any term they want of course.

I don’t see this as closed-world. I’m not enforcing that anyone shares this same understanding, I’m just saying this term means this thing. Anyone can then use that term if they want to and we would understand each other because we both know what it means.

Okay this is the first argument for JSON-LD I can understand. This does seem like a neat thing, though quite specific. Has this actually been used in the fediverse at any time? I guess not since nobody is using JSON-LD. I think JSON-LD’s method of making those equivalences is much too complicated (not a fan of the shorthand keys) and I’m not even convinced you need those equivalences. If this is what JSON-LD provides, it’s not worth it.

Honestly, I’d just let network effects do its work. If two implementations create separate terms for the same thing and eventually they start interacting, then I would guess over time they would settle on one of the terms. I don’t think you need a mechanism for stating that one term is equivalent to another. It seems to me that you’d need to state the equivalence in any case once you discovered that the two terms mean the same thing, so at that point you may as well just use the other term (or both for a while, deprecating one of them).

Maybe that is “closed world” to you. I don’t think it really is. It’s just human communication. Different languages merge over time or break off into dialects all the time. We still find a way to communicate.

And again, term conflicts like these are rare. Very rare I think - I haven’t heard of any actual conflicts so far in the fediverse. JSON-LD seems like a sledgehammer solution to a very small nail. Exact semantic equivalence between terms actually sounds somewhat as likely as UUIDs colliding (probably the former is more likely but still).

Nobody is suggesting a 1:1 mapping and certainly not any kind of centralized registry. I don’t know why you’d make such a strawman.

  • “possibly using multiple terms for the same concept”: I’m fine with this. People will figure this out I think.
  • “using the same term for multiple concepts”: This is not fine, but can’t happen if you use unique terms with no chance of collision (i.e. UUIDs again or whatever other mechanism you want).

So I say, let everyone extend freel,y creating whatever terms they want. If they make up the same terms, so be it. I say, that’s okay. Let them figure it out once they start communicating. And if they never communicate, well, they never need to worry about it.

This implies that JSON-LD provides an explicit definition of what a term means. In general, it does not. It maps terms to IRIs which may or not be URLs that can be dereferenced or resolved. It AP, it would have been interesting if the JSON-LD referred to an AP version of the AS2 OWL ontology, but that was abandoned long ago.

I think these statements that imply JSON-LD provides any significant semantics to terms (beyond basic @id, @value, @list, etc.) contribute to the confusion on the issue. Since JSON-LD is an RDF serialization, to understand it I think one must understand how semantics are defined in an RDF context (OWL, RDFS, etc.).

In any case, one alternative is to define the meaning of a JSON property with JSON Schema and a link to a “context” page that (probably informally) describes the property (or describe the property it in the schema itself). JSON-LD for a non-LD consumer is a net negative given JSON-LD does not support defining any structural and few value constraints.

3 Likes

Completely correct. The Ontology or Schema provides the meanings. It started being called “OWL Ontology” to make it sound like something obscure. But it is something fundamental. It is what defines the terms.

The context is a shorthand which saves typing and makes the JSON look neater, and in some cases, easier to parse (which is valuable!). But the context does not define the terms.

This is really a huge bug. They should just put the ontology back, as quite a lot of work went into it. Things do just about still work using the context, but things then tend to fall apart when you try and do extensibility, interop and new use cases. Would like to see this done in a future revision, as it was a big chunk of work in the original working group, and it’s all there and working, just needs to be linked properly to the specification docs.

1 Like

how? how and where? where and how? the problem with all of this is that there is a lot of “prior knowledge” required.

like, okay, i hear you loud and clear, you don’t find json-ld to be useful for the complexity it imposes. but what is the alternative for people who want to explicitly know things?

sure, a lot of this can be handwaved away if you’re only interested in “posting on the fediverse social networking platform” or whatever. it’s admittedly overkill when you’re only really interested in the text content plus some choice metadata. but if you want to do more than that, it’s really useful when linking between resources to know how they’re linked, what the relationship is, if any logical statements can be made from that, etc.

here’s an example: let’s say i want to tell the world that I like One Piece. how might i do that semantically?

well, at a basic level, i can just write it out in plain english:

"I like One Piece"

maybe give it some structure with a data format like AS2

{
"content": "I like One Piece"
}

maybe i can Create a Note saying this

{
  "actor": "https://trwnh.com",
  "type": "Create",
  "object": {
    "type": "Note",
    "content": "I Like One Piece"
  }
}

warning: most fedi implementations do not proceed beyond this point

but what if i wanted to be really precise?

{
"actor": "https://trwnh.com",
"type": "Like",
"object": "https://www.wikidata.org/entity/Q673",
"summary": "I like One Piece"
}

what if i wanted to be really, really precise?

{
"actor": "https://trwnh.com",
"type": "Like",
"object": "https://www.wikidata.org/entity/Q28667972",
"summary": "I like One Piece (specifically the manga, not necessarily the anime or the entire media franchise)"
}

now sure, i can be even more efficient if i’m only interested in the knowing and less in the delivery to my followers:

<https://trwnh.com> <http://xmlns.com/foaf/0.1/topic_interest> <https://www.wikidata.org/entity/Q28667972>

or translated to json-ld:

{
  "@id": "https://trwnh.com",
  "http://xmlns.com/foaf/0.1/topic_interest": [{"@id": "https://www.wikidata.org/entity/Q28667972"}]
}

or maybe i want to publish this action on the web as a standalone thing, but i want to be a polyglot and not implement just one spec

{
"@type": ["https://schema.org/LikeAction", "https://www.w3.org/ns/activitystreams#Like"],
"https://schema.org/agent": [{"@id": "https://trwnh.com"}],
"https://schema.org/object": [{"@id": "https://www.wikidata.org/entity/Q28667972"}],
"https://www.w3.org/ns/activitystreams#actor": [{"@id": "https://trwnh.com"}],
"https://www.w3.org/ns/activitystreams#object": [{"@id": "https://www.wikidata.org/entity/Q28667972"}]
}

*** context haters look away at this part ***

let’s add a basic context with prefixes so it’s a little more readable

{
"@context": {
  "as": "https://www.w3.org/ns/activitystreams#",
  "schema": "http://schema.org/"
},
"@type": ["schema:LikeAction", "as:Like"],
"schema:agent": [{"@id": "https://trwnh.com"}],
"schema:object": [{"@id": "https://www.wikidata.org/entity/Q28667972"}],
"as:actor": [{"@id": "https://trwnh.com"}],
"as:object": [{"@id": "https://www.wikidata.org/entity/Q28667972"}]
}

ok now let’s clean up the id-vs-value thing with proper term definitions

{
"@context": {
  "type": "@type",
  "as": "https://www.w3.org/ns/activitystreams#",
  "schema": "http://schema.org/",
  "Like": "as:Like",
  "LikeAction": "schema:LikeAction",
  "agent": {
    "@id": "schema:agent",
    "@type": "@id"
  },
  "actor": {
    "@id": "as:actor",
    "@type": "@id"
  },
  "object": {
    "@id": "as:object",
    "@type": "@id"
  },
  "schemaActionObject": {
    "@id": "schema:object",
    "@type": "@id"
  }
},
"type": ["LikeAction", "Like"],
"agent": "https://trwnh.com",
"schemaActionObject": "https://www.wikidata.org/entity/Q28667972",
"actor": "https://trwnh.com",
"object": "https://www.wikidata.org/entity/Q28667972"
}

*** context haters you can look again ***

now let’s move the context to another document so it’s not polluting every single object and is also reusable

{
"@context": "https://context.example",
"type": ["LikeAction", "Like"],
"agent": "https://trwnh.com",
"schemaActionObject": "https://www.wikidata.org/entity/Q28667972",
"actor": "https://trwnh.com",
"object": "https://www.wikidata.org/entity/Q28667972"
}

oh look, it’s valid AS2! a proper AS2 consumer should have no problem with this activity. so why do several fedi implementations still choke on this?

  • some fedi implementations will explicitly check for the activitystreams url in the context, even though they have no reason to do this and in fact shouldn’t do this at all!
  • some fedi implementations will get confused because type is an array, and they don’t know how to handle objects with multiple types correctly
    • some fedi implementations will get confused because they only check the first type, instead of checking all types for membership of the one type you’re interested in
    • some fedi implementations will get confused because they completely panic when they see type as an array, even though this is a perfectly normal thing to do
  • some fedi implementations will get confused because the object is not an AS2 type.

aside from marginally the first point here, none of these have to do with json-ld. in fact, you could remove the context entirely:

{
"type": ["LikeAction", "Like"],
"agent": "https://trwnh.com",
"schemaActionObject": "https://www.wikidata.org/entity/Q28667972",
"actor": "https://trwnh.com",
"object": "https://www.wikidata.org/entity/Q28667972"
}

and you still have basically all the problems above. is this document here not “plain JSON”?

like, the problem with extensibility in fedi software is just so clearly not json-ld. sure, json-ld makes it more complex because you have to handle multiple possible symbols for the same concept (depending on the context the producer compacted against or didn’t), but there are a few principles that AS2 lays out for handling extensions and they generally make sense:

  • if you encounter a term you don’t understand, you MUST NOT stop processing, you MUST ignore it and continue (which fedi mostly doesn’t, they drop the whole thing in violation of this requirement)
  • you SHOULD provide @context definitions if you use shorthand terms in your AS2 docs (which fedi mostly does, actually, they just make no use of it and definitions can be wrong due to cargo culting)
  • if you want full support for extensions, you MUST support compact uri expansion (fedi mostly doesn’t)
  • if you’re producing AS2 docs then you SHOULD avoid compact uris (for the above reason)
  • you SHOULD preserve properties exactly as you received them, if you reserialize AS2 docs for any reason (irrelevant, pretty much no one does this)
  • for LD reserializers (doubly irrelevant to fedi): you SHOULD preserve the original context and reuse it when reserializing

a lot of this is also explored in fep/fep/e229/fep-e229.md at main - fediverse/fep - Codeberg.org which is still a WIP but i’m really thinking of revisiting it quite soon in light of this thread.

still, the fundamental problem is that fedi implementations make a LOT of incorrect assumptions, even with AS2. taking away json-ld does little to nothing to address that issue.

the one thing i want to say in closing:

this is likewise the first argument that makes sense to me. it’s kind of a restatement of the classic YAGNI, but a mostly valid point – at least the second half of that statement is. the remaining dispute at that point is in exactly how to “figure it out”. (i still think n:n communication is not going to work forever, there WILL come a time when there are simply too many implementers, or when implementers may be unresponsive or otherwise not perceptive to working with you)

Yes and no.

It’s true that JSON-LD only provides trivial benefits for AP extensibility. However, some (like the AP Rec Editor) claim that extensibility is the primary benefit for non-LD use cases. AFAICT, there is no other claimed benefit for non-LD usage. That’s why non-LD developers are saying we should drop JSON-LD because it’s a net negative given it doesn’t help much with extensibility and has other issues that complicate non-LD interop.

The problem is not JSON-LD per se but the belief that it’s an effective extensibility solution for non-LD AP use cases. If a developer is creating an LD/RDF application, JSON-LD provides many potential benefits as a JSON-based RDF serialization. Almost all AP developers are not in that category.

A schema language (JSON Schema or similar) has the ability to define the structural and data constraints (provide more useful “context”) in a way that address many of the AP non-LD extensibility and interop issues you have discussed at length.

1 Like

How is there more prior knowledge required than when you use JSON-LD compared to plain JSON? I mean in JSON-LD land you still need to know what the term "https://www.w3.org/ns/activitystreams#actor" or "https://www.w3.org/ns/activitystreams#object" means. You need “prior knowledge” of those terms if you want to do anything with them (like saving an actor as a remote user in your database or whatever).

Unless you mean that these terms by virtue of being URLs will always link to some kind of machine-readable definition of the terms. But as I’ve said before, I don’t think such a machine-readable definition is useful. You can show that definition to a user maybe but I don’t see how that can lead to a good UX. And there might be terms that require side effects or some other mechanisms and a machine-readable definition will not help you to handle that. Also you don’t even know if the URL links to a definition or if it even stays alive forever.

So you ask how and where can someone define a term. I say:

  • How? In any way they want.
  • Where? Anywhere they want.

I don’t see any reason to put a particular restriction on how or where someone should define a term (as long as we ensure that terms do not collide so that people are free to define terms without fear of overlap).

For your “liking One Piece” example: I think it mostly made sense until the shorthands were introduced. I really don’t get the benefit of using the shorthands. Readability is just not important enough for that (and longform names can still be readable).

This is an unrelated tangent, but I’ve sometimes thought that including a type in the object might just be the wrong way to approach it? Like maybe we shouldn’t define a type field that then tells you something about what the other fields mean, but we should rather define that a certain type has certain fields. Then, when you encounter an object that has those fields, then it is of that type - you don’t need to state the type explicitly. Or maybe that doesn’t make sense at all and is just confusing, I dunno. It’s just a thought that I had. In any case, the inheritance in ActivityStreams is very unfortunate. We should not be using inheritance. /tangent

It is, but it is using bad term names. By using regular english words like “type”, “agent”, “actor” and “object”, you have a high chance of collisions with other terms (like an actor in a theatre or movie or something). So ideally you should instead make your keys unique either through a longer and more detailed name for the key, or by using a URI or a UUID or a UUID inside a URI or whatever you want, as long as you ensure that there is no chance of collision with other terms.

If you ask me, this is exactly the reason that JSON-LD is the problem with extensibility in fedi software. By making it more complicated to support extensions, nobody is supporting extensions properly. I feel I am repeating myself again, but if users do not use the feature properly or don’t understand the feature, the feature is bad (users being fedi software developers in this case). And there are other problems with JSON-LD’s extension mechanism, as was mentioned in that other Mastodon thread.

I don’t think anyone likes or is suggesting N:N communication. I agree that is not scalable. But I don’t think we require N:N communication just because we throw away JSON-LD. Do we need to specify any particular way of “figuring it out”? I don’t think we can do that or even want to.

For instance, should we specify how to handle different terms that mean the same thing (I feel the other potential problem, equal terms that mean different things, should be entirely avoidable by using unique terms/keys)? Should we specify what two implementations should do once they discover that two terms they are respectively using mean the same thing? I really don’t think the protocol needs to worry itself with that. Let those two implementations figure it out by themselves.

Would this be N:N communication? Only if you believe that all implementations will have overlapping terms with all other implementations. But, while theoretically possible, in practice I don’t think that happens. In practice, implementations will generally settle on common terms, because that is, after all, useful for communicating. So generally, different terms that mean the same thing will be rare.

This is a separate and distinct concept/mechanism that isn’t strictly part of JSON-LD, but it’s a common best practice.

I meant that you need to know that shorthand terms like actor mean https://www.w3.org/ns/activitystreams#actor and not http://schema.org/actor. The @context is explicit knowledge that actor is specifically https://www.w3.org/ns/activitystreams#actor, and explicit knowledge that its value is another node on the graph (@type: @id). If you remove the @context, then you are falling back to “implicit context” as shared between two parties. As a producer of documents, being explicit about what you mean might not matter if you limit your world to “only plain JSON consumers who already understand me”. Put another way, what JSON-LD allows you to do with @context is be explicit and unambiguous even while using shorthand terms. You can continue to use actor, and LD-aware processors know you mean https://www.w3.org/ns/activitystreams#actor. The annoying part for you is that you don’t know https://www.w3.org/ns/activitystreams#actor means the same thing as actor.

Honestly, it’s the kind of thing that makes me take the view that interchange and processing should happen in expanded form rather than compacted form. Put another way: JSON-LD should be about progressively enhancing “plain JSON” so that it can be converted into expanded form by LD-aware processors. The AS2 spec recommending JSON-LD for extensions should be more of a courtesy thing, where extension authors provide a context for “plain JSON” producers to include, while they can continue to ignore it. But on the consuming end, “plain JSON” consumers need to be able to expand the term to an IRI and work with the IRI instead. Think of it as a normalization step. If you see foo and you’re LD-unaware, you still need to know unambiguously what foo means. So AS2 says that for “full support” you MUST expand it to https://ns.example/foo instead of processing it as foo, because a different producer might use foo for something else. You still don’t need to be fully LD aware, you just need to support mapping terms to IRIs as a one-way operation. If you fail to do this one-way mapping then you can only ever at best have “partial support” with all the caveats discussed ad nauseam.

I realize that this is unwanted complexity, and people just want to work directly with foo just like they can work directly with native AS2 vocab. But it’s a necessary disambiguation step to allow “plain JSON” to understand shorthand terms. The alternative is to do away with the “MAY compact against other contexts” bit in AS2, meaning that all non-AS2 terms are only ever representable in their full IRI form.

In other words, this would be banned:

{
"@context": ["https://www.w3.org/ns/activitystreams", {"featured": {"@id": "http://joinmastodon.org/ns#featured", "@type": "@id"}}],
"id": "https://someone.example",
"type": "Person",
"featured": "https://someone.example/pinned-posts"
}

And this is the only thing that would be allowed:

{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://someone.example",
"type": "Person",
"http://joinmastodon.org/ns#featured": {"id": "https://someone.example/pinned-posts"}
}

This is something that would make implementing other specs that define their own @context more annoying, though, so I’m not even sure this would be better than what we have now. It just reduces the possible parsing you have to do (just get document["http://joinmastodon.org/ns#featured"]["id"], no having to expand shorthands because there are no other shorthands, no having to deal with possible string id references). But the problem is that if this extension ever gets added to the AS2 context via the SWICG Extensions Policy, then everyone has to switch over to using the new shorthand term to be compliant with AS2. It’s kind of like the equivalent of how in other systems, extensions will be trialled with a prefix like X- or -moz- or -webkit-, and then when they’re considered “stable” they drop the prefix, but that just means you end up having to support both for potentially forever. So I don’t think the SWICG Extensions Policy is 100% compatible with this hypothetical where we only compact against AS2.

Well, this makes your argument a lot clearer, but it does go very heavily against the idea that AS2 is “plain JSON”. The paradigm that AS2 follows is that you can ignore the @context and just use the AS2 vocab directly, but extensions are subject to expansion into IRIs via @context extensions. This effectively makes extensions inaccessible to “plain JSON” consumers until they implement the bare minimum of mapping terms to IRIs. Extensibility for “plain JSON” seems to be heading in this direction:

  • To fully support extensions, expand terms into IRIs
  • You can also wait until terms are added to the AS2 context (via the SWICG Extensions Policy) and then use those terms.

The other consequence here is that, just as ActivityPub on its own is not sufficient for a “social networking protocol”, so too is the AS2 context on its own not sufficient for a “social networking protocol”.

I think honestly if we want to push things forward in a “plain JSON” friendly way then the best way to do that would be to define a separate “conformance profile” for whatever idea of a “social network” and have it be defined in a FEP and come with its own context. That context can import the AS2 context. “Plain JSON” implementers would continue to be able to ignore @context, but there would need to be a governance mechanism for extending the “social networking protocol”'s context document. This basically amounts to a “soft fork” at worst and a “progressive enhancement” at best – the output is still AS2-compatible, it’s just not declaring https://www.w3.org/ns/activitystreams but instead declaring a superset. LD-aware processors should still be able to handle it, because the “protocol”/“profile” FEP gives them that courtesy. For LD-unaware processors the burden should be reduced in most cases because they have less and less things to consider as “extensions”, and more and more things to consider “in-protocol”. The hard part is getting implementers on board with this new “protocol” and its accompanying governance model (which should be considered very carefully). If multiple such “social networking protocol” profiles are developed as FEPs then the worst case is no different than what we have today.

s/Mastodon/(some subset of fediverse) but the point stands.

This is what I have sitting on my laptop, more or less:

FEP-993e: A conformance profile for social networking

## Goals

- define terminology like "account", "post", "conversation", and so on (or equivalents)
- define properties of each, and their expected shape and semantic interpretation
- define distribution methods
- define supported functionality and interactions

## Constituent FEPs for consideration

TODO: sort into required/recommended/optional?

...

{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://purl.archive.org/socialweb/miscellany",
{
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"featured": "toot:featured",
// ...
},
{
"Account": "https://w3id.org/fep/993e/Account",
"Post": "https://w3id.org/fep/993e/Post",
"Conversation": "https://w3id.org/fep/993e/Conversation",
// ...
},
// ...
]
}

We could use duck typing, sure, but the real value of types is using them as interfaces that are fulfilled by the current object. So for example, there’s no good way to describe “this collection specifically contains objects with content” except with a type that means exactly this. You could define some property that signals a shape constraint on every item in the collection, but it’s much easier to just say “yeah this is a Conversation”. But you can still leave some types defined by duck-typing, for example, an “actor” in AP is just something that has an inbox and an outbox, there’s no dedicated Actor type. You can similarly duck-type a Collection based on whether it has items, or duck-type an Activity on whether it has an actor, and so on. Actually, if you had machine-readable definitions of each of these types/classes, you could use logical inference to know with certainty that something is a certain type, even if it doesn’t declare itself to be. For example, no one goes around declaring that every object is an Object, right? Anyway,

is definitely doable with rdfs:domain, which defines exactly that – that “the presence of this property implies that the subject is of a certain class”. This is expressed in the AS2-Vocab spec as the “Domain” of a property. But this is mostly lost on fedi implementers, who in many cases do strict type checking, and in several cases with extreme inflexibility. For example, in Mastodon, it’s not enough for something to have an inbox and outbox and even followers in order to be considered followable, it also needs to be explicitly one of Person, Group, Organization, Application, Service. Or in Lemmy, they expect all communities to be Group, and all submissions to be Page, and so on. These are all things that aren’t really founded in anything except for various misinterpretations of the specs. Nonetheless, they make up the implicit de facto “protocol” for each software project. It’s already incredibly difficult to tease out all these assumptions from the documentation of each project, because they often go unstated, or the human reader happens to miss them while reading the documentation… but to do this for multiple projects over and over is maddening. It’s why I keep saying that JSON-LD is far from the biggest issue that fedi has in implementing any sort of “protocol”. The biggest issue is that there isn’t any such “protocol” – it’s all ad hoc, and many things are neglected along the way. So you get the de facto “Mastodon protocol” because it happens to be the largest and most well-documented approach. (Sometimes I wonder how things would have played out differently if I hadn’t written the Mastodon documentation at the time that I wrote it…)

1 Like

In my imagined plain JSON version of AP, you would not use keys like “actor” as they are much too general. You’d only use keys that are specific and unique, so that there is no overlap in terms. You don’t need to know that “actor” is a shorthand for some longer term because you would just use the longer term and there would be no confusion. There should be no shorthands or multiple representations of keys at all. Sorry if this wasn’t clear from what I described earlier.

So again, my solution here is, don’t use shorthand terms. I don’t want to work with foo! I want to work with https://specific.domain/very/specific/path/foo or foo:f8360cc9-160e-442f-93c2-0e0dd17f8fdd. We should be specific and unique. Even the “base” fields of the spec should be that way. I feel like it’s a mistake that the AS2 vocabulary gets some kind of special treatment in the current system and all other vocabularies/terms/extensions are kind of a second-class citizen.

The “base” of the spec should be on the same level as any other “extension”. There should be a very thin, small specification for how to define terms and maybe some very basic common stuff, but everything else should be built as if it was an extension (or maybe many extensions) to the spec. That (or those) extension(s) would probably be widely-used and well-supported, but it should in theory be replacable without changing the core of the protocol. This should also ensure that implementers really support extensions better than they currently do (there wouldn’t really be much to support in this case though, you’d just use the extensions you understand and ignore everything else). This is not really possible right now as AS2 has a kind of special status and is part of the base of ActivityPub. As far as I understand, you can’t choose to not support AS2 terms?

In my vision of ActivityPub 2.0, you should be able to throw away the “base extensions” and use something else if you prefer. Maybe fewer implementations would understand you but maybe you think your model is better and maybe more implementations would move towards your model over time. I hope that makes sense.

2 Likes

So, this thread has been interesting. There’s certainly a lot of stuff we would like to change. However, the path forward seems… hard to say the least. As @silverpill said near the start of the thread:

I get the sentiment - of course breaking changes are uncomfortable. It would require a lot of work from implementers, who would probably need to support both AP 1.0 and AP 2.0 for a while, potentially a very long time. Existing FEPs and other related stuff would probably not function at all for AP 2.0.

But is keeping AP 1.0 at all costs for perpetuity a viable path forward? As a potential implementer, I personally don’t see myself working on an AP implementation in the protocol’s current state as explained here. I am some 5.5k LOC into an implementation (not very far but hey) and this has forced me to learn about the current problems of AP, and I feel like giving up as the problems are too big to buy into. There’s also the fact that the longer we wait, the higher and higher the cost for switching will become.

What would switching even look like? We’d need to align (how?) on basically an entirely new protocol, which would need to be written (how? where? by who?). Even calling it AP 2.0 might be disingenuous as it would probably be nothing more than a spiritual successor. We’d need extremely wide agreement to avoid a fracturing of the ecosystem into AP 1.0 and AP 2.0. We’d then need implementers to support AP 1.0 and AP 2.0 at the same time for a while, deprecating AP 1.0. Then, after probably a long time, implementers could remove AP 1.0 support. But does this even sound realistic?

By the way, surely this is not the first time desired changes have been discussed? Are there any older discussions anywhere that touch on any more problems or desired changes?

2 Likes

Yes. At least if we are talking about backward-incompatible changes listed in the summary thread: Summary: Desired changes for a future revision of ActivityPub and ActivityStreams - #2 by SorteKanin

I don’t see anything there that requires major backward incompatible changes. For example:

  • JSON-LD is already not required (according to the ActivityPub spec). This only needs to be articulated more clearly.
  • Issues with software switching are solved by data portability.
  • HTTP signatures are not part of AP spec, embedded signatures are specified in FEP-8b32
  • A separate inbox can be used for receiving batched activities
  • The work on developing a better standard for federated groups is ongoing

The real challenge is implementing these things in a coordinated way. This is what we’re trying to do here on SocialHub and with FEPs.

Regarding FEP-ef61, I’m not sure it’s really a solution though. Only implementations using the same kind of portable IDs can be moved between, and that really doesn’t sound likely to happen, and it leaves current implementations (which are not using those kind of IDs) in the dust with no migration path.

People can migrate from non-portable IDs to portable IDs with a Move activity.

If some developers don’t want to support portability, we can’t do anything about it, that’s correct. But you can always choose a better software.

If I understand this correctly, this only migrates actors and only really handles followers of actors. So any other objects (of which there are many, like every single activity or follow request and so on) are still left with old IDs.

Well, if the spec specified a single unified ID scheme instead of letting everyone choose their own IDs, then it wouldn’t be a problem and everyone would be required to support changing implementations on the same domain from the start. It seems like a major oversight that this is not in the base specification and I don’t see a way to fix it in a backwards-compatible manner.

I think it is possible: make all identifiers relative to the actor, or “the actor’s namespace”. This only requires servers to be aware of “the actor’s namespace” (and to assign identifiers within it).

This doesn’t solve the problem of “old identifiers” floating around, but that’s a much bigger problem that has no real solution. It’s called “link rot”.

Regarding “the actor’s namespace”: "The actor's namespace" · Issue #443 · w3c/activitypub · GitHub calls it out as an issue that should be worded out of the spec, but maybe we should actually formalize it a la fep/fep/e3e9/fep-e3e9.md at main - fediverse/fep - Codeberg.org or some similar mechanism.

I don’t really understand that FEP-e3e9. The example suggests that every actor would need a personal site (in the example, alice-personal-site.example) which could persist while the actor moves instances/domains? Or am I misunderstanding something? Having an extra domain like that doesn’t sound viable for every user.

I don’t think link rot is related to this problem. Link rot is a totally separate issue that would happen regardless of what you do. The fact that every single AP server has their own unique ID scheme (or is at least allowed to have that) is a problem that is only caused by the current AP spec.

you wouldn’t need an entire site, you just have your actor be an endpoint that takes relative urls and resolves them. then you just point at that endpoint, using for example the DID service and relativeRef mechanism. we could have other mechanisms that work similarly i.e. providing a variable base uri with relative references. the two components you need are an identity provider and a storage provider.

more practically, you can go from identity.example/people/sortekanin/actor to activitypub.example/actors/25c06d88-39d3-4150-972b-1a45ba7ff8c8 and then from there you have a relative reference /activities/35d2ea93-1693-4c40-af7e-20c8d99a2b59. if you want to move from activitypub.example to cooler-activitypub.example then you can do that without breaking all your id links which are assigned against identity.example/people/sortekanin/actor – and cooler-activitypub.example doesn’t have to be aware of the old uri structure because it’s encoded in the relative reference. in that sense, your “actor data” can be seen as a repository that can be copied from server to server.

if for some reason you didn’t want to use DIDs then you could still do it with HTTP redirects. have the application running at identity.example/people/sortekanin/actor handle requests by removing your “actor namespace” and appending the rest to your current actor endpoint. the end result looks like this:

GET /people/sortekanin/actor/activities/35d2ea93-1693-4c40-af7e-20c8d99a2b59 HTTP/1.1
Host: identity.example

HTTP/1.1 307 Temporary Redirect
Location: https://cooler-activitypub.example/actors/25c06d88-39d3-4150-972b-1a45ba7ff8c8/activities/35d2ea93-1693-4c40-af7e-20c8d99a2b59 HTTP/1.1
GET /actors/25c06d88-39d3-4150-972b-1a45ba7ff8c8/activities/35d2ea93-1693-4c40-af7e-20c8d99a2b59 HTTP/1.1
Host: cooler-activitypub.example

HTTP/1.1 200 OK

{
  "id": "https://identity.example/people/sortekanin/actor/activities/35d2ea93-1693-4c40-af7e-20c8d99a2b59"
}

as far as the activitypub server is involved, it should know to assign new identifiers within the namespace https://identity.example/people/sortekanin/actor instead of assigning them on activitypub.example – that way when you move to cooler-activitypub.example your links don’t break. and cooler-activitypub.example doesn’t need to follow the same structure as activitypub.example. this all works off of the assumption that identity.example is more stable and long-lived than either activitypub.example or cooler-activitypub.example, and that activitypub.example and cooler-activitypub.example are aware of the identity and storage components.