Pre-FEP: How to claim that an Object is part of a given Collection

From a conversation with @thisismissem, @erincandescent, and @bengo on the fediverse, it was brought up that having a way to reverse-link an object to any containing collections would be useful for a “follow-your-nose” way of navigating to various collections of interest. streams is defined as “various collections of interest”, but it is not semantically appropriate here because it is intended for actors to publish “streams”. context could work, but being part of a collection isn’t necessarily purposeful in the way context defines itself. So the following approaches are considered:

Define itemOf as an inverse property of items

:itemOf a rdf:Property;
rdfs:label "itemOf";
rdfs:comment "Indicates that the object is included in a collection";
rdfs:domain as:Object;
rdfs:range as:Collection;
owl:inverseOf as:items.
{
"@context": [
  "https://www.w3.org/ns/activitystreams",
  {
    "itemOf":
    {
    "@id": "https://w3id.org/fep/xxxx/itemOf",
    "@type": "@id"
    }
  }
],
"id": "https://domain.example/some-object",
"type": "Object",
"itemOf": "https://domain.example/some-collection"
}

Considerations

  • Requires OWL reasoning to pick up on the “inverse property” relation with items.
  • The (flattened) graph doesn’t make any claims about the Collection and which items it contains. It just links to it by id.

Examples

Compacted without context:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://domain.example/some-object",
  "type": "Object",
  "https://w3id.org/fep/xxxx/itemOf": {
    "id": "https://domain.example/some-collection"
  }
}

Flattened without context:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "@graph": [
    {
      "id": "https://domain.example/some-object",
      "type": "Object",
      "https://w3id.org/fep/xxxx/itemOf": {
        "id": "https://domain.example/some-collection"
      }
    }
  ]
}

Flattened with context:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "itemOf": {
        "@id": "https://w3id.org/fep/xxxx/itemOf",
        "@type": "@id"
      }
    }
  ],
  "@graph": [
    {
      "id": "https://domain.example/some-object",
      "type": "Object",
      "itemOf": "https://domain.example/some-collection"
    }
  ]
}

Statements being made:

@base <https://domain.example/> .
@prefix as: <https://www.w3.org/ns/activitystreams#> .

<some-object> a <Object> .
<some-object> <https://w3id.org/fep/xxxx/itemOf> <some-collection> .

Define itemOf as the @reverse of items

{
"@context": [
  "https://www.w3.org/ns/activitystreams",
  {
    "itemOf":
    {
    "@reverse": "as:items",
    "@type": "@id"
    }
  }
],
"id": "https://domain.example/some-object",
"type": "Object",
"itemOf": "https://domain.example/some-collection"
}

Considerations

  • The (flattened) graph doesn’t make any claims about the object, it instead makes the claims about the collection (and the collection is therefore included in the graph as a subject).
  • Does not require defining a term, since you can reuse items with @reverse
    • If a context is not used, then you will have to deal with the @reverse key and its structure.

Examples

Compacted without context:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://domain.example/some-object",
  "@reverse": {
    "items": "https://domain.example/some-collection"
  },
  "type": "Object"
}

Flattened without context:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "@graph": [
    {
      "id": "https://domain.example/some-collection",
      "items": "https://domain.example/some-object"
    },
    {
      "id": "https://domain.example/some-object",
      "type": "Object"
    }
  ]
}

Flattened with context (note that this is the same as without context, and it doesn’t restore the itemOf property in the flattened object):

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "itemOf": {
        "@reverse": "as:items",
        "@type": "@id"
      }
    }
  ],
  "@graph": [
    {
      "id": "https://domain.example/some-collection",
      "items": "https://domain.example/some-object"
    },
    {
      "id": "https://domain.example/some-object",
      "type": "Object"
    }
  ]
}

Statements being made:

@base <https://domain.example/> .
@prefix as: <https://www.w3.org/ns/activitystreams#> .

<some-object> a <https://www.w3.org/ns/activitystreams#Object> .
<some-collection> <https://www.w3.org/ns/activitystreams#items> <some-object> .

Comparing the above approaches

I guess it comes down to whether you want to represent the inverse claim either indirectly (as in the case of defining a property) or directly (as in the case of using JSON-LD @reverse). The indirect claim would be made on the object, and the direct claim would be made on the collection.

You could even use two different terms to define both senses separately? Perhaps itemOf can refer to the inverse property of items, while reverseItems can refer to the JSON-LD @reverse?

Posting this to get feedback before submitting it as a FEP.

I think I like the itemOf property better than the @reverse syntax (since that’s requiring more knowledge of json-ld, complicating parsing for JSON clients.

2 Likes

This would be helpful if it can be made to work. But I have a couple of concerns. First is that itemOf is the only really viable way to get value out of this, given that AS/AP explicitly disclaim dependence on LD processing.

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.
[…]
Implementations MAY also use additional properties and values not defined in the JSON-LD @context with the understanding that any such properties will likely be unsupported and ignored by consuming implementations that use the standard JSON-LD algorithms.
Activity Streams 2.0

The other is that this introduces the same kind of unanswered questions as the inReplyTo property, in that there’s no way to know if the object claiming to be an itemOf: some-collection is actually contained in some-collection, except to enumerate the whole collection. I would very much like to have an answer to that question before spreading the problem out further.

1 Like

This is definitely a concern, and it is due to properties not being “facts” but instead “claims”. There’s some work in fep/fep/0391/fep-0391.md at main - fediverse/fep - Codeberg.org in using result of an Activity to point to the side effect being successfully carried out, for example having a Create.Object.inReplyTo trigger Add<Object>.target<Object.inReplyTo.replies> should be reflected in Create.result probably. There’s also some work in pointing to this same Add activity with properties like inReplyToProof and contextProof, as well as a desire to have some way of going from an Object to its corresponding Create. FEP-0391: Special collection proofs is the discussion thread for that. (Side note, there should probably also be some consideration for signaling some kind of undoneBy property or a way to otherwise Update an Add to signal that it shouldn’t be used for such attestation, i.e. that its purpose as a stamp has been revoked.)

Even with this in mind, I still think that right now we have no way of going from an object to a collection, so we can’t even begin to verify this claim because we can’t even make this claim. We only have claims for specific semantic usages, like inReplyTo implying possible inclusion in the inReplyTo.replies collection, and context implying possible inclusion in a context collection (if context resolves to a collection). But even if you don’t verify these as bidirectional claims, they still exist as singly-linked claims – an object’s inclusion in a replies collection doesn’t have any bearing on whether the object is in fact a response to the inReplyTo, and an object’s inclusion in a context collection doesn’t have any bearing on whether the object is in fact contextual to that context.

So likewise, having collections claim certain items, and having items be able to claim certain collections, is something that should be verified in both directions, but each side of that claim continues to exist separately from the bidirectional claim (which needs verifying).

Another thing that could help is support for querying collections, either broadly/generally with something like SPARQL (which makes a lot of sense for LD-aware clients, but might be overkill and overly complicated for LD-unaware clients), or something far more narrow/specific like Smithereen’s collectionSimpleQuery which doesn’t do anything other than tell you if an object is included in a collection.

2 Likes

In the specific use case I’m tackling (FediMod FIRES label providers), the collection size should be fairly bounded, in that I wouldn’t expect a single label provider to have more than 100 items, so can comfortable exist as a non-paged collection.

This has me thinking about how that new HTTP method proposal of QUERY could be used to support SPARQL or similar against a Collection without a dedicated endpoint

Something like collectionSimpleQuery would be ideal, if it’s a property of the collection rather than some third object.