FEP-844e: Capability discovery

Capability discovery for ActivityPub applications.

This document is based on the idea described in FEP-aaa3: Listing Implemented Specifications on the Application Actor.

1 Like

Added the list of implementations and a new capability to the registry (RFC-9421 + Ed25519):

https://codeberg.org/fediverse/fep/pulls/629

@feps

@silverpill Is it only related to HTTP capabilities or this can be extended to FEPs as well?
@silverpill @feps

@rayslava It can be extended to FEPs. However, capabilities usually can be inferred from properties and types (e.g. Question type indicates that server can accept poll votes), and in those cases explicit signaling is not necessary.

@feps

Why not simply use Nodeinfo for this? Implementing this generator for every actor type and then sending it for thousands of accounts is unnecessarily wasteful.

Edit: Okay it seems like something similar can be achieved via fep-d556 via webfinger, that wouldnt be difficult to add then.

Why not simply use Nodeinfo for this?

NodeInfo has also been discussed, but some developers and server operators don't want to publish software metadata and usage stats. The solution proposed here seems to be less controversial.

1 Like

re: Link vs the actual thing (@id)

i am not sure Link objects is the way to go. it makes more sense to me to just use the actual @id because then you can make descriptive statements about that thing out-of-band and load them into your processor.

re: registries

if you are establishing a registry then it would be useful to have precise definitions for precise identifiers. it is unclear what exactly https://datatracker.ietf.org/doc/html/rfc9421 entails, and https://datatracker.ietf.org/doc/html/rfc9421#name-eddsa-using-curve-edwards25 hints toward “eddsa using curve edwards25519”, but that is a similarly incomplete definition – it tells you how to sign some input, but not what that input should be. you would want to know the full parameters that might make up a full auth-scheme that can be signaled with the HTTP WWW-Authenticate header in cases of authentication, or included in HTTP Accept-Signature in case of inbox OPTIONS or on a failed POST.

implications for FEP process?

tangentially to this: should the FEP process have any affordances for registries? it could complicate things like “what FINAL status means” and “how to count implementers”, so it’s worth considering. i think individual FEPs can declare their own “capability identifiers” as well, since individual FEPs have the ability to contain subresources (like term definitions and so on) and act as their own namespaces.

canonical or equivalent identifiers

also tangentially, the datatracker.ietf.org URIs used are not canonical for the actual RFCs. there are several equivalent identifiers that could refer to the same thing (owl:sameAs):

  • doi:10.17487/RFC9421 (the DOI)
  • https://doi.org/10.17487/RFC9421 (the DOI resolver)
    • https://dx.doi.org/10.17487/rfc9421 (another DOI resolver)
  • https://www.rfc-editor.org/info/rfc9421 (what the actual DOI resolves to)
  • urn:ietf:rfc:9421 (the URN for the IETF RFC)
  • urn:issn:2070-1721 (the URN for the ISSN)
  • https://www.ietf.org/rfc/rfc9421 (an alternate resource)
    • https://www.ietf.org/rfc/rfc9421.html (in text/html)
    • https://www.ietf.org/rfc/rfc9421.xml (in text/xml)
    • https://www.ietf.org/rfc/rfc9421.txt (in text/plain)

it may be wise to use a canonical identifier and also have it actually be an identifier so that equivalences can be defined and recognized wherever they might be used. see Service Discovery Identities and Service Discovery Features for prior art.

RFC 9421 actually defines an IANA registry for this in Section 6.2: HTTP Message Signature – maybe use this to form a canonical identifier? something like #alg/ed25519 or #alg:ed25519 could work, maybe even just #ed25519 if you expect there to never be a conflict between algorithm names and parameter names. you could also define something specific in a FEP, though.

1 Like

Could you provide an example? We can use an object with url property instead of Link, but I don’t see what is the benefit.

In case of RFC-9421 and RFC-9421+ed25519 capabilities, the identifiers are opaque strings. There is no document that defines how to use those signatures in Fediverse, but when such document will be written, it will be added to the registry.

I think registries fit quite well into the FEP process. Once the proposal is FINAL, you can add new entries, but you can not change them.

I agree, that would be great.

I’d like to keep these identifiers until a FEP or a report is published where the corresponding capabilities are described in detail. After that we can mark datatracker.ietf.org identifiers as deprecated.

I think capability identifiers should be HTTP URIs. Self-describing, if possible.

instead of

{
  "href": "https://datatracker.ietf.org/doc/html/rfc9421"
}

it would be

{
  "id": "https://datatracker.ietf.org/doc/html/rfc9421"
}

a processor could then be able to say, “hey, i know what that id is, i have some definitions right here!” and apply logic according to those definitions. the processor would know that your <software> <implements> <rfc9421> (and <https://datatracker.ietf.org/doc/html/rfc9421> owl:sameAs <rfc9421>) instead of only knowing that your <software> <implements> _:b0 and then separately that _:b0 as:href <https://datatracker.ietf.org/doc/html/rfc9421>. the former is saying “software implements rfc9421” and the latter is saying “software implements a Link whose href is rfc9421”. it doesn’t make much sense to say the software implements a Link; rather, it is implementing the RFC, which is the thing being referred to (<> in Turtle, @id in JSON-LD).

yes, the ids are opaque strings, but people can use many different identifiers for the same thing. we want at least one of the following to be true:

  • people use the same id consistently globally (enforced by a central registry)
  • people recognize when different ids refer to the same thing

encoding the knowledge into Turtle looks like this:

<rfc9421>
  owl:sameAs
    <https://datatracker.ietf.org/doc/html/rfc9421>,
    <doi:10.17487/RFC9421>,
    <https://doi.org/10.17487/RFC9421>,
    <https://dx.doi.org/10.17487/rfc9421>,
    <https://www.rfc-editor.org/info/rfc9421>,
    <urn:ietf:rfc:9421>,
    <urn:issn:2070-1721>,
    <https://www.ietf.org/rfc/rfc9421>.

and this encoding would allow an OWL reasoner to recognize any of the above ids as referring to the same thing, for maximum robustness on the consuming side. you can imagine a similar encoding into a programming language like Python, using something like RFC9421_SUPPORTED = thing_implemented in set("https://datatracker.ietf.org/doc/html/rfc9421", ) and so on.

of course, on the publishing side, we don’t want to encourage people to use a bunch of different ids. this is where the central registry comes in. a FEP could act like one, or it could establish one. if you imagine an id like https://w3id.org/fep/xxxx/#rfc9421 and https://w3id.org/fep/xxxx/#rfc9421-ed25519 (with definitions similar to the above Turtle codeblock), then this hypothetical FEP-xxxx can refer back to https://w3id.org/fep/844e (or other such FEPs for establishing capabilities).

they would be, still. unfortunately discourse’s formatting made that invisible. to clarify, i was describing https://www.iana.org/assignments/http-message-signature/#alg/ed25519 or https://www.iana.org/assignments/http-message-signature/#ed25519 as the full id.

one slight problem with most URIs that could be used to represent the RFC itself: those URIs are owned by other social entities, so we can’t strictly say with full authoritativeness that any of them refer to the RFC, if we want everyone to agree (global consensus). we can only do this with statements about an identifier that we own, in a context that we establish. so a fep can be trusted to make authoritative statements about its own subresources, and this provides the single self-describing identifier as the entrypoint.

i don’t know if there’s anything that can be done at the https://w3id.org/fep/a4ed level or if this is more in the purview of https://w3id.org/fep/888d, but it seems like a pattern worth documenting somewhere…

{ "id": "https://datatracker.ietf.org/doc/html/rfc9421"}

But https://datatracker.ietf.org/doc/html/rfc9421 is not an ActivityPub object, it's an HTML document. This is why Link is used.

We can make it explicit by adding mediaType: text/html to each item, but I feel that it would be redundant. I am not against clarifying this in the FEP, though. For example:

The value MUST be an URI, preferably pointing to a document where the capability is described.

there’s nothing saying @id has to be jsonld or even rdf – an @id with an html representation is still fine, you just can’t easily get machine-readable information about it without maybe natural-language extracting it from the html. https: resources aren’t guaranteed to have any specific content-type returned. this is typically discussed as “what the identifier identifies” – in the http[1] protocol it is a resource you can GET/POST/etc but in rdf’s data model it is just an opaque string that could refer to anything. you compare exact string matching of the lexical form and that’s it.

the thing is, this can be described in any Content-Type, not just text/html.

if the intent is to provide html documentation of capabilities then one option is a blank node (with no id or an id starting with _:), just some as:url pointing to that html documentation:

{
  "id": "_:b1",
  "url": [
    {
      "id": "_:b2",
      "type": "Link",
      "href": "https://datatracker.ietf.org/doc/html/rfc9421",
      "mediaType": "text/html"
    }
  ]
}

this is an indirect way of saying “the thing that has a url which is a Link with href … and media type text/html”. _:b1 is the blank node identifier for the thing (the capability), _:b2 is the blank node identifier for the link. these get stripped out just like null-valued properties in the internal graph model.

at the application layer, you would still have to recognize when two capabilities are “the same thing”. you might say “anything with this as:url is the same capability”. you could store capabilities and dedupe them by url[*].href but it is more direct to index them by id. the point of an identifier is to facilitate reuse when referring to the same thing.

in other words, you would consider these to be “the same thing”:

{
  "id": "urn:ietf:rfc:9421",
  "url": [
    {
      "href": "https://datatracker.ietf.org/doc/html/rfc9421",
      "mediaType": "text/html"
    }
  ]
}
{
  "url": [
    {
      "href": "https://datatracker.ietf.org/doc/html/rfc9421",
      "mediaType": "text/html"
    }
  ]
}

the 1st one is saying “the thing referred to by urn:ietf:rfc:9421 has a url whose href is … and whose media type is text/html”. the 2nd one is saying “the thing which has a url whose href is … and whose media type is text/html”. if “has a url whose href is … and whose media type is text/html” is a sufficiently unique description, then you’ve uniquely identified the capability. if more than one capability can share the same as:url as:Link as:href, then you need to disambiguate[2].

so by saying something like this:

{
  "href": "https://datatracker.ietf.org/doc/html/rfc9421"
}

if you end up using this to index by href, then href is actually behaving like @id and not as:href:

{
  "@context": {"href": "@id"},
  "href": "https://datatracker.ietf.org/doc/html/rfc9421"
}

because href here is identifying the capability, not simply serving as some as:Link. the important thing about @id (or id as it’s defined in AS2) is it refers to the thing; it is a Reference.

the reason as:Link exists is to be able to talk about the link as separately from the thing it’s referring to. “this is not rfc9421, this is a link to rfc9421” can be a useful thing to be able to say, but for capability negotiation you want to be able to say “i support rfc9421”[3] and not “i support a link to rfc9421”.


  1. for https: identifers, “what the identifier identifies” is typically understood via the “uri definition discovery protocol” UriDefinitionDiscoveryProtocol - W3C Wiki or Understanding URI Hosting Practice as Support for URI Documentation Discovery which basically simply says “do an HTTP GET and see what comes out” (via Link header with rel=definedby or rel=describedby, 303 See Other redirect, 200 OK with some rdf-bearing representation, or 200 OK implicitly identifying the returned content (which can have zero or more representations)) ↩︎

  2. it’s like saying “the Person whose Mailbox is joe@mail.example” versus “the Mailbox joe@mail.example”. you don’t actually need to identify the Person with their own id if you have a unique property, but it helps to talk about the Person as separately from the Mailbox. ↩︎

  3. what “rfc9421” means is a separate concern. you can make a series of descriptive statements about how to define that term (rfc9421 is an RFC; its title is HTTP Message Signatures; …), and you can make a series of descriptive statements about what “support rfc9421” entails (the range of the capabilities property implies rfc9421 is a Capability; if :x supports rfc9421 then :x has some other properties; …) ↩︎

I’d like to see something like this implemented by Fedi servers. My current use cases are:

  • Server-side C2S capability advertisement to allow graceful degradation or adaptive UIs on the client side, where the client is not server implementation-specific.
  • Test support so that server tests for unsupported features can be skipped automatically.

Comments/Questions:

  1. I agree with @trwnh that a set of URIs (vs an ordered list of Links) makes more sense. [Side question: why the ordered list?] The anonymous objects referenced by implements in the examples are not explicitly typed as Link. Although the text says it MUST be a Link, that doesn’t help automatic processors.
  2. Like @trwnh said, the JSON-LD @id property type doesn’t require the URI to resolve to an AP document or any document at all (in the URN case, for example). It means the property references a node in the “graph”. That node can have other information attached to it (summary, description, implementation status, … whatever). A URL (vs URI) declared as “xsd:anyURI” would not have that ability since it’s a value rather than a node identifier.
  3. For my purposes, I’d like a hierarchy of capabilities where support can be declared at different levels. For example, Mastodon supports inbox PUSH for specific types of activities, but doesn’t support inbox GET at all. I understand this may be a bit more fine-grained model than you have in mind. This feature could potentially be implemented using the existing FEP, maybe with an associated FEP to define a set of well-known URIs that support the hierarchical model. [Side note: A test suite could even discover the capability model, which could potentially be served out-of-band by third-party services]
  4. The examples show capabilities advertised in the actor object, but the capabilities are associated with the Application. It would be useful (for the C2S use case) if the capabilities were per actor since an actor might configure of specific capabilities (opt-in/out, for example). The implements property would be a good fit for actor capabilities, but something like capabilities might work there. Is that something you would consider, maybe in addition to the server-level advertisement?
2 Likes

i think with ids being opaque these would be different capabilities, but we could say that some capabilities are e.g. skos:broader or skos:narrower than other capabilities? using the FEP as an example, “RFC 9421” is skos:broader than “RFC 9421 with ED25519”, and “RFC 9421 with ED25519” is skos:narrower than “RFC 9421”. but this kind of hierarchy lives and dies by what the identifiers mean and what they entail – for example, does “RFC 9421” entail support for every alg? so i think defining capabilities in a FEP actually makes more sense than using ietf.org or rfc-editor links directly – which would allow FEP authors to explicitly define what identifiers mean. (this could also interface with the “implemented” type of FEP, but in a more decentralized manner since you can declare capabilities in a self-documenting way…

my personal take re: this is that capabilities belong not on the actor or necessarily on some Application/Service, but on the inbox itself. loosely speaking, an actor’s “capabilities” are dependent on what their inbox supports, and if an actor changes inboxes then their “capabilities” probably change too. secondarily, you might care about capabilities of an Application that is attached to the inbox, i.e. checking activities that arrive in the inbox and performing actions based on that – the “side effects” that get described in activitypub can be thought of as a “default app” attached to the inbox and performing those actions in an automated manner. monoliths like mastodon could be thought of as having exactly 1 such actor attached to all inboxes.

They are different URIs representing capabilities at different levels of a capability hierarchy.

However, URI’s are not necessarily opaque in all contexts. One could argue they are not completely opaque in any context (for example an id without a scheme will not be considered a URI by a JSON-LD parser). URIs have a defined top-level structure that include a scheme and most URI schemes have well-defined substructures and those could be further interpreted in specific contexts (a directory hierarchy interpretation of an HTTPS URL path slashes, for example).

I’m currently using URNs for capabilities (but open to other options). A standard URN has a scheme, (urn:), a namespace identifier (like apcap, for example), and a namespace-specific string. The latter can be further structured to represent a hierarchy (would need documentation of the structure). There’s no reason why the set of capabilities couldn’t be a combination of these URNs and other URIs like the ones mentioned in the FEP. However, those would not have the ability to represent a capability hierarchy (without further specification).

It’s a list of link objects and not URIs because we may want to add additional properties to them.
It’s an ordered list because JSON arrays are ordered (although I think the order shouldn’t matter).
Items are not typed as Link because that makes presentation more compact and the presence of href already indicates that an object is a link. However, you can add type to your capabilities, this is not forbidden.

Is there a practical reason for using id? This would be a breaking change, and I don’t want to do that just for the sake of theoretical purity.

Additional information could be attached to a link too.

I like the idea of encoding hierarchy in URI (URN) structure. But this probably should be a topic of another FEP.

I think user’s preferences should be clearly separated from application capabilities. This FEP is focused on the latter.

However, clients should be able to advertise their capabilities too. Application is a good way to represent a client, but I don’t know what to do if actor uses multiple clients.

That doesn’t require Link objects, but I’m not. suggesting removing the Link objects but only suggesting not requiring them,

JSON-LD supports both ordered (@list) and unordered collections (@set). If ordering doesn’t matter, implements should be declared as a @set(which is the default for an @id type if there’s no collection type specified in the context).

The implementsproperty is already declared in the context to be a collection of @id. The question is why the FEP requires those IRIs to refer to Link objects even if no property beyond href is provided in the actor serialization (versus in a external LD document).

It’s not a breaking change to not require all capabilities to be Link objects. They would still be allowed. It just seems like an unnecessary requirement rather than a “theoretical purity” issue (not sure what that means in this case).

For C2S client purposes, knowing a server implementation has capabilities A, B and C is not particularly useful if an actor has disabled A and B or if some of those capabilities are disabled by server configuration.

Thanks for the information.

i think nodeinfo.metadata (the metadata object is a wildwest place though, there is no standard or data structure format, etc.) is one place for it. i personally prefer to have a separate endpoint to cater for service itself to announce its capability. the inspiration i got from is xmpp’s mod_disco: XEP-0030: Service Discovery

so i propose endpoints something like this:

  • https://example.com/disco#activitypub
  • of maybe this: https://example.com/activitypub/disco

while service itself has a list of capability. individual users within the service platform can choose to announce whether this capability should be seen & interacted with publically or not.

ultimately, setting things up this way grants users more control (within the list of whatever capabilities the service platform can offer)

this is quite a complicated setup i have in mind. i have no idea what real world use case atm. just my thought for now.

I see. That makes sense, I will remove the word Link from the requirement.

We need to give it more thought. Perhaps there are situations where ordering does matter?

Let’s say there are two similar capabilities. For example, RFC9421+EdDSA and RFC9421+ECDSA. A server wants to indicate that both are supported, but RFC9421+EdDSA is preferable. It seems that in this case we can use ordering to express the preference.

The context document may be incorrect. Should I replace @id with something else?

Software capabilities can’t be disabled by a user. Things that can be changed by a user, such as manuallyApprovesFollowers, should not be added to implements array.

The FEP has an “Intended use” section with the following text:

The reliance on the mechanism described in this document might increase implementation complexity and hinder interoperability.
Implementers MUST NOT use it in cases where capabilities can be inferred from properties and types of objects.

.well-known endpoints are better because some ActivityPub servers may already use /disco or /activitypub prefixes for something else.

oh yah, that’s good point.