Background
@fedify/vocab-runtime ships a handful of JSON-LD contexts as preloaded documents so the default document loader can resolve them without a network round-trip. The current set in packages/vocab-runtime/src/contexts.ts includes ActivityStreams, the w3id.org/security/* family, DID v1, WebFinger, schema.org, GoToSocial, FEP-5711, and http://joinmastodon.org/ns. The joinmastodon entry in particular was added in #631 specifically because the URL never served a real JSON-LD document; Mastodon has always inlined the term definitions, and some ActivityPub implementations (Bonfire was the reported case) still put the bare URL in @context, causing downstream JSON-LD processors to fail with 404 Not Found.
The gap
Every Lemmy-originated activity references https://join-lemmy.org/context.json in its @context array. That URL does serve a document, unlike the joinmastodon case, but join-lemmy.org returns it as Content-Type: application/json without the Link: <...>; rel="http://www.w3.org/ns/json-ld#context" header that @fedify/vocab-runtime's default loader treats as a non-JSON-LD response. The loader rejects the document, so every Lemmy activity (including the inbound Follow that kicks off a subscription) fails to parse before the application's handlers ever run.
Applications that want to interoperate with Lemmy, Mbin, or any other software that uses this context therefore end up writing the same workaround: copy the context file into their own repo, then pass a custom documentLoaderFactory that short-circuits the URL to the bundled copy. The threadiverse tutorial currently in review as #710 spends an entire sub-section, Bundle Lemmy's JSON-LD context, walking readers through about 40 lines of boilerplate to do exactly that. Every non-trivial Lemmy-interoperable Fedify app needs to duplicate it today.
Proposal
Add https://join-lemmy.org/context.json to preloadedContexts, in the same shape as the joinmastodon entry:
- Save the current document body at packages/vocab-runtime/src/contexts/join-lemmy.json. The published file is 33 lines at time of writing; it only extends ActivityStreams and
w3id.org/security/v1 with Lemmy-specific terms (postingRestrictedToMods, moderators, matrixUserId, and friends) plus a few common co-fediverse extensions (toot:, litepub:, pt:). It does not redefine as: or the bare Public term.
- Register it in packages/vocab-runtime/src/contexts.ts under the key
https://join-lemmy.org/context.json, with a comment explaining why we preload it (mirror the joinmastodon comment's shape).
- Add a CHANGES.md entry under the unreleased
@fedify/vocab-runtime section, styled the same as the joinmastodon entry in the 1.3.0 section.
Why this matters now
The threadiverse tutorial is the first piece of Fedify documentation that explicitly walks readers through writing their own document loader. It works, but it's a spike in complexity right in the middle of the Lemmy-compatibility chapter, and the underlying reason ("join-lemmy.org's content-type is wrong") is uninteresting noise for the reader. Preloading the context upstream turns the chapter into a single-line callout ("Lemmy's activities parse without any extra configuration").
Follow-up in the threadiverse tutorial and example
Once this lands, #710 can drop the workaround before it merges:
- Remove the Bundle Lemmy's JSON-LD context sub-section from docs/tutorial/threadiverse.md under the Making the community actor Lemmy-compatible chapter.
- Remove federation/lemmy-context.json and the
BUNDLED_CONTEXTS/documentLoaderFactory block from federation/index.ts in the paired fedify-dev/threadiverse example.
Both are straightforward deletions; they can be a top-up commit on #710's branch.
Out of scope
- Tracking upstream changes to the Lemmy context document. If join-lemmy.org ever updates the file, we refresh the bundled copy the same way the GoToSocial context was updated in 1.3.2 (see the CHANGES entry in that section).
- Adding bundled contexts for other threadiverse implementations (Mbin, Piefed, NodeBB). If they serve their own
@context URLs with the same problem, each one can be its own follow-up.
Acceptance criteria
Background
@fedify/vocab-runtimeships a handful of JSON-LD contexts as preloaded documents so the default document loader can resolve them without a network round-trip. The current set in packages/vocab-runtime/src/contexts.ts includes ActivityStreams, thew3id.org/security/*family, DID v1, WebFinger, schema.org, GoToSocial, FEP-5711, andhttp://joinmastodon.org/ns. The joinmastodon entry in particular was added in #631 specifically because the URL never served a real JSON-LD document; Mastodon has always inlined the term definitions, and some ActivityPub implementations (Bonfire was the reported case) still put the bare URL in@context, causing downstream JSON-LD processors to fail with404 Not Found.The gap
Every Lemmy-originated activity references
https://join-lemmy.org/context.jsonin its@contextarray. That URL does serve a document, unlike the joinmastodon case, but join-lemmy.org returns it asContent-Type: application/jsonwithout theLink: <...>; rel="http://www.w3.org/ns/json-ld#context"header that@fedify/vocab-runtime's default loader treats as a non-JSON-LD response. The loader rejects the document, so every Lemmy activity (including the inboundFollowthat kicks off a subscription) fails to parse before the application's handlers ever run.Applications that want to interoperate with Lemmy, Mbin, or any other software that uses this context therefore end up writing the same workaround: copy the context file into their own repo, then pass a custom
documentLoaderFactorythat short-circuits the URL to the bundled copy. The threadiverse tutorial currently in review as #710 spends an entire sub-section, Bundle Lemmy's JSON-LD context, walking readers through about 40 lines of boilerplate to do exactly that. Every non-trivial Lemmy-interoperable Fedify app needs to duplicate it today.Proposal
Add
https://join-lemmy.org/context.jsontopreloadedContexts, in the same shape as the joinmastodon entry:w3id.org/security/v1with Lemmy-specific terms (postingRestrictedToMods,moderators,matrixUserId, and friends) plus a few common co-fediverse extensions (toot:,litepub:,pt:). It does not redefineas:or the barePublicterm.https://join-lemmy.org/context.json, with a comment explaining why we preload it (mirror the joinmastodon comment's shape).@fedify/vocab-runtimesection, styled the same as thejoinmastodonentry in the 1.3.0 section.Why this matters now
The threadiverse tutorial is the first piece of Fedify documentation that explicitly walks readers through writing their own document loader. It works, but it's a spike in complexity right in the middle of the Lemmy-compatibility chapter, and the underlying reason ("join-lemmy.org's content-type is wrong") is uninteresting noise for the reader. Preloading the context upstream turns the chapter into a single-line callout ("Lemmy's activities parse without any extra configuration").
Follow-up in the threadiverse tutorial and example
Once this lands, #710 can drop the workaround before it merges:
BUNDLED_CONTEXTS/documentLoaderFactoryblock from federation/index.ts in the paired fedify-dev/threadiverse example.Both are straightforward deletions; they can be a top-up commit on #710's branch.
Out of scope
@contextURLs with the same problem, each one can be its own follow-up.Acceptance criteria
https://join-lemmy.org/context.jsonentry present in thepreloadedContextsmap with an explanatory comment.@fedify/vocab-runtimesection.preloaded contextstest in packages/vocab-runtime/src/docloader.test.ts still passes (and the new entry is exercised by it).