diff --git a/docs/ideas/split-domain-handles.md b/docs/ideas/split-domain-handles.md new file mode 100644 index 0000000..203f89d --- /dev/null +++ b/docs/ideas/split-domain-handles.md @@ -0,0 +1,55 @@ +# 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. diff --git a/docs/roadmap.md b/docs/roadmap.md index f870f40..30d555a 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -89,3 +89,4 @@ See `docs/ideas/` for pre-spec explorations: - `activity-participants.md` — tag co-riders on shared activities, confirm/decline, federated mentions - `multi-day-collections.md` — activity collections for multi-day trips (architecture open question #1) - `fediverse-enhancements.md` — post-v1 federation: map images in Notes, fediverse kudos, comments, Events/Mobilizon, Wanderer interop, federated explore +- `split-domain-handles.md` — `@bob@example.com` handles while the journal runs on a subdomain (Hollo/Mastodon-style; Fedify supports it natively) diff --git a/openspec/changes/route-federation/design.md b/openspec/changes/route-federation/design.md index ca5c672..b32c011 100644 --- a/openspec/changes/route-federation/design.md +++ b/openspec/changes/route-federation/design.md @@ -71,6 +71,11 @@ activities are neither delivered to nor accepted from non-trails instances. (The Wanderer-interop idea would widen this allowlist — explicitly out of scope here.) +Split-domain interop: the check runs against the **actor IRI's host** +(server domain) after WebFinger resolution, never the handle's domain — +remote instances may run Hollo/Mastodon-style split domains. See +`docs/ideas/split-domain-handles.md`. + ### Decision: Sync check is a pg-boss cron, owner-driven re-fetch Daily job on the *mirror-holding* instance: for each mirror whose diff --git a/openspec/changes/social-federation/tasks.md b/openspec/changes/social-federation/tasks.md index 34346ca..d41da8d 100644 --- a/openspec/changes/social-federation/tasks.md +++ b/openspec/changes/social-federation/tasks.md @@ -46,6 +46,7 @@ ## 6. Outbound follow + trails-to-trails check - [ ] 6.1 Update `followUser` to allow remote IRIs; before creating, fetch the remote actor object and inspect `software`/discovery endpoint + > Split-domain interop constraint (2026-06-07): resolve the handle via WebFinger first, then run the NodeInfo/`software` check against the **actor IRI's host** (the server domain), never the handle's domain — Hollo/Mastodon-style split-domain instances (`@user@example.com` served from `social.example.com`) would otherwise be wrongly refused. See `docs/ideas/split-domain-handles.md`. - [ ] 6.2 Implement `/.well-known/trails-cool` endpoint on our side (publishes our software identity) so other trails instances recognize us - [ ] 6.3 If the discovery check fails, return 4xx with a clear "outbound federation to non-trails instances isn't supported yet" message + docs link - [ ] 6.4 If the check passes, write follow row with `accepted_at = NULL`, push `Follow` activity to remote inbox