trails/docs/ideas/split-domain-handles.md
Ullrich Schäfer bf907976c4 docs: split-domain handles idea + interop constraint for the trails check
Prompted by Hollo's split-domain setup. Two parts:

- docs/ideas/split-domain-handles.md: offering split handle/server
  domains to self-hosters is config-level work — Fedify's origin
  option natively accepts { handleHost, webOrigin } — and handles are
  permanent identity, so apex handles matter. Single-domain stays the
  default (Decision #18); post-launch polish.
- Interop constraint pinned in social-federation task 6.1 and
  route-federation's gating decision: the trails-to-trails NodeInfo
  check must run against the actor IRI's host after WebFinger
  resolution, never the handle's domain — split-domain remote
  instances would otherwise be wrongly refused.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:18:13 +02:00

2.5 KiB

Split-domain handles for self-hosters

Pre-spec exploration (2026-06-07), prompted by Hollo's split-domain setup (https://docs.hollo.social/install/split-domain/).

What it is

Letting the handle domain differ from the server domain: @bob@example.com while the journal runs at trails.example.com. The apex only has to serve (or redirect) /.well-known/webfinger; actor IRIs, inbox/outbox, and the web UI all live on the server domain. Mastodon (WEB_DOMAIN/LOCAL_DOMAIN), GoToSocial (host/account-domain), and Hollo all support this.

Why it matters for trails.cool specifically

  • Fediverse handles are permanent identity — you cannot migrate @bob@trails.example.com to @bob@example.com later. Self-hosters who care about their handle want the apex in it from day one.
  • Most apexes are taken (a website, an existing service): the operator case in this very project — social.ullrich.is exists because ullrich.is was occupied; split-domain would have allowed @ullrich@ullrich.is.
  • A self-hosting-first platform that forces "handle = wherever you can host a Node app" is leaving its best identity feature on the table.

Why it's cheap for us

Architecture Resolved Decision #18 (single domain) was justified as "avoids WebFinger complexity" — but Fedify natively supports the split: the origin option we already pass to createFederation accepts { handleHost, webOrigin } instead of a string (since Fedify 1.5). Our side would be roughly:

  1. Optional HANDLE_DOMAIN env (defaults to DOMAIN — Decision #18 stays the default)
  2. Pass origin: { handleHost: HANDLE_DOMAIN, webOrigin: ORIGIN }
  3. Audit the places that build handles vs URLs (localActorIri builds URLs — already correct; acct: construction, users.domain column semantics, profile @user@domain rendering)
  4. Self-hoster docs: one redirect rule on the apex (/.well-known/webfinger → server domain), like Hollo's guide

Interop direction (already a constraint, not an idea)

Regardless of whether WE offer split domains, remote instances use them — the trails-to-trails check must run against the actor IRI's host after WebFinger resolution, never the handle's domain. Pinned in social-federation task 6.1 and route-federation's gating decision.

Scope guess

Config + audit + docs + a two-domain test in the (future) two-instance integration environment. Post-launch self-hosting polish; pairs with the broader self-host packaging story.