C2S/S2S Update Issues

Continued from Stricter specifications for pagination of Collections and OrderedCollections - ActivityPub / Client to Server - SocialHub

In the example I referenced, the two objects definitely don’t have the same set of properties or the same values for those properties. Given an existing Note:

  {
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://example.test/note-1",
    "type": "Note",
    "content": "Hello",
    "summary": "A greeting."
  },

The “id” property (an alias of JSON-LD “@id”) is the unique URI of a specific object/resource, a Note in this case.

And given a C2S Update activity for that Note:

  {
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://example.test/update-1",
    "type": "Update",
    "object": {
      "id": "https://example.test/note-1",
      "content": "Howdy",
      "name": "greeting-note",
      "summary": null
    }
  }

This is a graph with two resources. The first resource is an Update activity. The activity resource references a second resource via the “object” property. The second resource is a… I’m going to call it an update description. It has instructions about how to update the existing Note. In this case, it is instructing the server to modify the “content” property, add the “name” property and delete the “summary” property.

The second resource (the update description) also has an “id” property, which means that’s the URI of that resource. But, of course, it’s not. It’s the URI of the update’s target object. That’s the first problem. I’m suggesting that an activity “target” property should be used for this purpose instead. The update description resource probably doesn’t even need an “id” in most cases. It could be referenced via an RDF blank node.

The second issue is that AP requires all objects to have a “type” property. I left that out of the example intentionally since there is no AP-defined type for an update description (and an extension didn’t feel necessary for the example). Putting “Note” in the type property (JSON-LD “@type”) is not correct. It’s not a Note, it’s an update description.

The third issue is the null value for “summary”. The AP specification says this should be interpreted as a property deletion instruction. However, that doesn’t play well with JSON-LD.

A map entry in the body of a JSON-LD document whose value is null has the same meaning as if the map entry was not defined.

The JSON-LD parsers I’ve experimented with (including the JSON-LD Playground) do indeed strip out the null-valued instruction from the expanded Update activity (the “summary” property is dropped).

The fourth issue occurs when the Update subgraph is merged with the existing AP graph. The incorrect URI for the update instructions has undesirable results. You can see this if you simulate the graph in the example by combining the existing note and the activity in a list and entering it into the Playground. You’ll see that the Note IRI now has multiple “content” values. You might be able to do some special processing to compensate for that, but it’s not necessary if the “target” property is used.

There are no examples of C2S Update activities in the specification, so using the “target” property for the update target is compliant AFAICT. The S2S Update is a significantly different scenario.

Are you saying that someone implementing C2S Update behavior should follow the S2S Update semantics? If so, the specification clearly says they are different (partial update versus replacement). There are somewhat different, but similar issues with assigning the “object”'s replacement description with the URI of the replacement target. Your example is fine for one update, but after the second update, both of the previous Update activities will reference the Note state following the second replacement. If someone is looking at the first Update activity it’s not going to be what was originally posted. This is where this topic slightly overlaps with the collection discussion. By using “target” for the replacement target, the historical replacement description (the “object” referent) can remain immutable.

Update should have its object inlined or embedded in its entirety, so that the “complete replacement” logic can be followed as described in the S2S Update section.

Is that always true? What about something like the following?

  {
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://example.test/update-1",
    "type": "Update",
    "object": "https://example.test/myobject-version-3",
    "target": "https://example.test/myobject"
  }

This would tell the server to replace the existing “https://example.test/myobject” with the content of “https://example.test/myobject-version-3” (maybe the remote server has a special versioning feature). The object could be inlined as a performance optimization, but it could also be dereferenced from the remote server.

1 Like

If it’s a C2S Update, it should not have an id. C2S activities don;t specify an id; the id is assigned by the server after the activity has been POSTed to the outbox.

No, I’m saying that C2S Update and S2S Update are different; the server should process a C2S Update, but publish an S2S Update. C2S Update follows “partial update” semantics. S2S Update does not.

This is wrong. target is not defined on activities of type Update. If you’re dealing with Collections, you use Add / Remove / Move. These activities have a defined target.

This is possible with a different activity (someone proposed as:Replace once) but I’d hesitate to recommend it. This particular point runs tangent to the other discussion about dereferencing a Create from a different/remote domain: Who says objects cannot be created elsewhere?

Other relevant discussions:

I agree the C2S client wouldn’t provide an activity URI. There are two “ids” in the example activity. One for the activity (assume it is the server-assigned URI or remove it, it doesn’t change the example) and the “id” of the “object”. The second one is the issue and if it’s not supplied by the client (in some property) how does the server know which object is the update target?

The AS2 specification defines Activity as the domain of the target property. My suggestion is consistent with that model. The AP specification requires a target for Add and Remove (Move isn’t mentioned in the AP spec), but there’s nothing to prevent using target in other activities.

Rereading the AS2 vocabulary spec, it looks like AP changed the semantics of Update a bit (and maybe caused some of these issues as a side-effect.)

[Update] Indicates that the actor has updated the object. Note, however, that this vocabulary does not define a mechanism for describing the actual set of modifications made to object.

(Emphasis mine.) However, AP does define a mechanism for describing the set of modifications. This seems like a subtle change to the semantics of object in the Update activity. In AS2, it’s the URI of the object that changed and the server must dereference the URI to get the new state. In AP, the object is the set of modifications and the server is expected to apply those changes to an existing target object.

The AS2 description of Update goes on to say:

The target and origin typically have no defined meaning.

That makes sense if the Update is a simple notification that a resource has changed (without specifying how it changed). Given the AP definition of Update, I think at least target should have a defined meaning.

Yes, that’s why I intentionally made the object and target be the same domain (and probably the same actor or attributedTo for authorization purposes).

Thanks for the links. There is some overlap with those topics. I used the word “version” in the last example, but that was a mistake on my part. I’m not talking about versioning or revision histories (other than immutable historical activities, which are only somewhat related). It is interesting that Pieter is also discussing using target with as:Update, but with slightly different semantics than what I’m suggesting.

Hi Steve. I do not believe that the ActivityPub specification of the Update activity is sufficient to actually implement it. One can implement “something”, but just looking at what changing recipients entails, one realizes one either needs:

  • A complicated algorithm
  • Or a quite strict list of stuff being allowed to change [1]

So I think that writing up how Updates can work would be really helpful. In particular, I think that using your target / object pattern will lead to something more understandable than the current spec.

One more remark: I consider sections " 6.2.1 Object creation without a Create Activity" and " 6.3.1 Partial Updates" of the ActivityPub spec, good cases for “Delete and release as ActivityPub 1.1”. They very much feel like a random optimization that does not solve a problem. For example for Updates, I consider it the most trivial best practice to fetch the object before editing, to avoid race conditions. So the client has the full object to edit. Always sending the full object avoids all the awkwardness of deciding if a field was deleted or just not updated.

Thanks.

[1]: If one considers Mastodon, the recipients are determined from the content through mentions, so one cannot change the content.

1 Like

You can, though… that’s definitely something you can do.