diff --git a/apps/journal/app/lib/legal.ts b/apps/journal/app/lib/legal.ts
index 9105096..fc2a9be 100644
--- a/apps/journal/app/lib/legal.ts
+++ b/apps/journal/app/lib/legal.ts
@@ -16,4 +16,4 @@ export const TERMS_VERSION = "2026-04-19";
* require re-acceptance (the policy is informational, not contract), so this
* is display-only — not persisted.
*/
-export const PRIVACY_LAST_UPDATED = "2026-04-20";
+export const PRIVACY_LAST_UPDATED = "2026-06-07";
diff --git a/apps/journal/app/routes/legal.privacy.tsx b/apps/journal/app/routes/legal.privacy.tsx
index ce4b14a..303f26c 100644
--- a/apps/journal/app/routes/legal.privacy.tsx
+++ b/apps/journal/app/routes/legal.privacy.tsx
@@ -260,6 +260,22 @@ export default function PrivacyPage() {
, der ohne Query-Logs arbeitet. Eine selbst gehostete Instanz ist
geplant.
+
+ Föderation (ActivityPub) – Wenn Ihr Profil auf{" "}
+ public steht, ist Ihr Konto über das
+ ActivityPub-Protokoll föderiert: Andere Server (z. B.
+ Mastodon-Instanzen oder andere trails.cool-Instanzen) können Ihr
+ öffentliches Profil (Anzeigename, Nutzername, Bio, öffentlicher
+ Signaturschlüssel) abrufen und Ihnen folgen. An die Server
+ angenommener Follower übermitteln wir Ihre als{" "}
+ public markierten Aktivitäten (Titel, Statistiken,
+ Link). Diese Server liegen außerhalb unserer Kontrolle; was dort
+ mit zugestellten Inhalten geschieht (Speicherung, Anzeige,
+ Weiterverbreitung), richtet sich nach deren Richtlinien.
+ Rechtsgrundlage ist Ihre Einwilligung durch das aktive
+ Veröffentlichen (Art. 6 Abs. 1 lit. a DSGVO). Private Profile
+ föderieren nicht.
+
BRouter – Routenberechnung läuft auf einer von uns
selbst gehosteten Instanz. Keine Weitergabe an Dritte.
@@ -452,6 +468,53 @@ export default function PrivacyPage() {
+
+
+ Federation (ActivityPub)
+
+
+ Applies only when your profile visibility is public.
+ Private profiles do not federate at all — no actor object, no
+ WebFinger, no inbox.
+
+
+ -
+ Your actor object (fetchable by any fediverse server) exposes:
+ username, display name, bio, profile link, and your public
+ signing key. Never your email, never private content.
+
+ -
+ Activities you mark
public are pushed to the
+ servers of your accepted remote followers and listed in your
+ public outbox. Once delivered, copies live on those servers
+ under their policies — un-publishing sends a retraction, but
+ remote deletion cannot be guaranteed (and remote servers that
+ processed a retraction will not re-show a later re-publish).
+
+ -
+ Signing keys: one RSA keypair per user. The private key is
+ stored encrypted at rest and never leaves this server.
+
+ -
+ Remote actor cache: for accounts that interact with this
+ instance we store their public profile basics (handle, display
+ name, inbox/outbox URLs, public key) to render follower lists
+ and feeds without re-fetching.
+
+ -
+ Remote content: public (or followers-only) activities from
+ trails users you follow on other instances are cached here for
+ your feed — followers-only items are shown only to the
+ follower whose follow brought them in.
+
+ -
+ Inbox traffic (signed requests from other servers) appears in
+ the standard server logs (14-day retention, see section 4) and
+ is rate-limited per source instance.
+
+
+
+
Sentry
diff --git a/docs/deployment.md b/docs/deployment.md
index f4998e8..3a785ed 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -102,6 +102,66 @@ curl -sf https://trails.cool/api/health && curl -sf https://staging.trails.cool/
Plan it as a short maintenance window (~2–3 min downtime); don't ship
network-option changes expecting the workflows to apply them.
+## Federation runbook
+
+Federation (ActivityPub via Fedify) is gated by `FEDERATION_ENABLED=true`
+per environment. Currently: staging **on**, production **off**. The full
+change history and design live in `openspec/changes/social-federation/`.
+
+### Enabling on an environment
+
+1. Ensure `FEDERATION_KEY_ENCRYPTION_KEY` is in `secrets.app.env`
+ (SOPS) — keypair generation fails closed in production without it.
+2. Set `FEDERATION_ENABLED=true` in the environment's compose env (see
+ `cd-staging.yml` for the staging pattern).
+3. First boot with the flag on enqueues `backfill-user-keypairs`
+ (generates RSA keys for existing users — idempotent) and registers
+ the federation jobs (`deliver-activity`, `poll-remote-actor`,
+ `poll-remote-outboxes`, `federation-kv-sweep`).
+4. Smoke: `curl -s "https:///.well-known/webfinger?resource=acct:@"`
+ (200 for a public user, 404 otherwise) and `/nodeinfo/2.1`.
+
+`FEDERATION_LOG_LEVEL=debug` turns on Fedify's per-request signature
+diagnostics (staging runs debug during soaks; dial back to `info`).
+
+### Key rotation
+
+Rotating `FEDERATION_KEY_ENCRYPTION_KEY` requires re-encrypting every
+`users.private_key_encrypted` (decrypt with old, encrypt with new) —
+there is no script yet; write one against
+`apps/journal/app/lib/federation-keys.server.ts` primitives when first
+needed. Rotating a single user's keypair: NULL their key columns and
+let the backfill regenerate (remotes pick the new key up from the actor
+document; deliveries signed with the old key fail until then).
+
+### Abuse monitoring
+
+- Inbox is rate-limited 60 req/5 min per source instance
+ (`federationSourceHost`); 429s show in journal logs.
+- Outbound is paced (1 req/s delivery, 1 req/5 s polling per host).
+- Watch `docker logs ` for `fedify·federation` lines; Loki
+ picks them up. No per-instance blocklist yet — see
+ `docs/ideas/instance-administration.md` (manual emergency lever:
+ block the source IP/host in Caddy).
+
+### Troubleshooting deliveries
+
+Hard-won checklist from the 2026-06-06/07 soak (details in
+`openspec/changes/social-federation/design.md`):
+
+1. **Test both IP families from inside the journal container** before
+ suspecting signatures (`family: 6` vs `4` — dangling A records and
+ v6-only instances are common; our containers have IPv6 since
+ #466/#468).
+2. Remote shows the post count but no posts → remotes never backfill
+ outbox history; only pushed or individually-fetched objects appear.
+3. Delivered but invisible, no errors anywhere → check the remote's
+ `tombstones` table; a previously-retracted URI is refused forever.
+4. Mastodon gives deliveries a 10s read timeout — anything synchronous
+ and slow in the inbox path breaks federation silently.
+5. Actor changes (profile fields, keys) need the remote to re-fetch:
+ `tootctl accounts refresh user@domain` on a Mastodon you control.
+
## cd-brouter manual trigger
```bash
diff --git a/openspec/changes/social-federation/tasks.md b/openspec/changes/social-federation/tasks.md
index 5e5eced..3684b5b 100644
--- a/openspec/changes/social-federation/tasks.md
+++ b/openspec/changes/social-federation/tasks.md
@@ -76,9 +76,9 @@
## 10. Privacy + docs
-- [ ] 10.1 Update privacy manifest: federation traffic, remote actor cache, what data is exposed in the actor object (display name, avatar, public key)
-- [ ] 10.2 `docs/deployment.md`: federation runbook — feature flag, key rotation procedure, abuse monitoring, troubleshooting
-- [ ] 10.3 Soften the Journal home "Federated by design" blurb on flagship now (or align it to "ActivityPub federation: inbound + trails-to-trails outbound" once this lands)
+- [x] 10.1 Update privacy manifest: federation traffic, remote actor cache, what data is exposed in the actor object (display name, avatar, public key)
+- [x] 10.2 `docs/deployment.md`: federation runbook — feature flag, key rotation procedure, abuse monitoring, troubleshooting
+- [x] 10.3 Soften the Journal home "Federated by design" blurb on flagship now (or align it to "ActivityPub federation: inbound + trails-to-trails outbound" once this lands)
## 11. Testing
diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts
index 347aaba..a1c8d84 100644
--- a/packages/i18n/src/locales/de.ts
+++ b/packages/i18n/src/locales/de.ts
@@ -192,7 +192,7 @@ export default {
},
federation: {
title: "Föderiert by design",
- body: "Deine Journal-Instanz spricht via ActivityPub mit anderen trails.cool-Servern. Folge Freundinnen und Freunden überall.",
+ body: "Teil des Fediverse via ActivityPub: Mastodon-Nutzer:innen können dir folgen und deine öffentlichen Aktivitäten sehen, und du folgst trails-Nutzer:innen auf jeder Instanz.",
},
ownership: {
title: "Deine Daten, immer",
diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts
index 26b26a3..f6ee07f 100644
--- a/packages/i18n/src/locales/en.ts
+++ b/packages/i18n/src/locales/en.ts
@@ -192,7 +192,7 @@ export default {
},
federation: {
title: "Federated by design",
- body: "Your Journal instance talks to other trails.cool servers via ActivityPub. Follow friends anywhere.",
+ body: "Part of the fediverse via ActivityPub: Mastodon users can follow you and see your public activities, and you can follow trails users on any instance.",
},
ownership: {
title: "Your data, always",