Skip to content

Object metadata serialization - #320

Merged
aaronjae22 merged 3 commits into
mainfrom
object-metadata-serialization
Oct 9, 2026
Merged

aaronjae22 merged 3 commits into
mainfrom
object-metadata-serialization

Conversation

@aaronjae22

Copy link
Copy Markdown
Collaborator

In #295 we added to Noteseven metadata columns (summary, to, cc, in_reply_to, url, source and previously) but they were not being served.

This is a Note today with all seven populated served by the content collection to a bound token:

{
  "@context": "<https://www.w3.org/ns/activitystreams>",
  "type": "Note",
  "id": "<http://testserver/api/notes/1/>",
  "actor": "<http://testserver/api/actors/1/>",
  "content": "<p>copied</p>",
  "published": "2016-05-01T12:00:00+00:00",
  "visibility": "public"
}

The destination is not able to keep the metadata the source never sends. LOLA asks the source to send it (§6.3) and the destination to preserve it (§7.1.5–§7.1.8). We are not able to properly test the transfer work that follows until the source serves it.

There are a few more stuff going on in the same payloads:

  1. Activities drop their breadcrumbs too. Create, Like and Follow all store previously and none of them serves it.
  2. A Note says actor. AS2 defines actor only on Activities, and a Note is an Object, not an Activity. liked_collection already says attributedTo for the same Note so basically today one Note comes out under two different keys depending on which endpoint serves it.
  3. previously has no usable JSON-LD definition. The Actor lists the LOLA spec's web address as a context but that address serves an HTML page. A JSON-LD processor must load every entry in @context and stops at the first one that fails which means today the whole Actor fails to process
ACTIVITY_STREAM_CONTEXT = "https://www.w3.org/ns/activitystreams"
LOLA_CONTEXT = "https://swicg.github.io/activitypub-data-portability/lola"
BLOCKED_CONTEXT = "https://purl.archive.org/socialweb/blocked"

# Return the extended context used specifically for Actor responses
# Includes blocked collection support (FEP-c648)
def build_actor_context():
    return [
        ACTIVITY_STREAM_CONTEXT,
        BLOCKED_CONTEXT,
        LOLA_CONTEXT
    ]

As served:

[AS, BLOCKED, LOLA]: FAILED -> Dereferencing a URL did not result in a valid JSON-LD object…
[AS, BLOCKED] (LOLA removed)  : OK, 13 properties

I was doubting about including this change (it has been on my backlog for quite some time) and its importance right now but since I was working on previously I decided to add it.

Most fediverse servers read our JSON as plain JSON and never notice. A destination developer who checks our payloads in a JSON-LD tool gets an error from the reference implementation.


Current

  • A Note serves 7 keys whatever is stored, labelled with actor
  • Activities never serve previously
  • The Actor's @context is [AS2, blocked, ]

After this PR

  • A Note serves the same 7 keys, labelled attributedTo, plus each metadata field that has a value. An empty field is left out entirely and it's never sent as null or []
  • Activities serve previously when they have breadcrumbs
  • previously is defined inline in @context, and only on objects that carry it. The Actor carries it always, because the Actor always serves previously.

⠀Expected output for the same Note after the change:

{
  "@context": [
    "<https://www.w3.org/ns/activitystreams>",
    {"previously": {"@id": "<https://swicg.github.io/activitypub-data-portability/lola#previously>", "@type": "@id", "@container": "@list"}}
  ],
  "type": "Note",
  "id": "<http://testserver/api/notes/1/>",
  "attributedTo": "<http://testserver/api/actors/1/>",
  "content": "<p>copied</p>",
  "published": "2016-05-01T12:00:00+00:00",
  "visibility": "public",
  "summary": "CW: a copied note",
  "to": ["<https://lemongrove.example/followers>"],
  "cc": ["<https://oakfrost.example/brock>"],
  "inReplyTo": "<https://lemongrove.example/notes/parent>",
  "url": "<https://lemongrove.example/@aurora/1>",
  "source": {"content": "copied", "mediaType": "text/markdown"},
  "previously": [{"actor": "<https://lemongrove.example/>", "id": "<https://lemongrove.example/notes/1>"}]
}

@aaronjae22
aaronjae22 requested a review from lisad October 5, 2026 18:55
@aaronjae22 aaronjae22 self-assigned this Oct 5, 2026
@aaronjae22
aaronjae22 added this pull request to stack #321 October 5, 2026 18:55
@aaronjae22
aaronjae22 marked this pull request as ready for review October 5, 2026 18:56
"actor": build_actor_id(activity.actor.id, request),
"published": activity.timestamp.isoformat(),
"visibility": activity.visibility,
**_non_empty(activity, {"previously": "previously"}),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is nice

I also like the idiom
**if_not_empty(activity)
**if_not_empty({"previously": previously})

because it makes the template very readable with one item per line in a clear order

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll switch the helper in the in the follow-up PR, I like it

Base automatically changed from unique-object-ids to main October 9, 2026 16:07
@aaronjae22
aaronjae22 force-pushed the object-metadata-serialization branch from 8db9312 to d2c66ce Compare October 9, 2026 16:07
@aaronjae22
aaronjae22 merged commit 18ae5c5 into main Oct 9, 2026
3 checks passed
@aaronjae22
aaronjae22 deleted the object-metadata-serialization branch October 9, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants