feat(journal): audience-aware social feed with remote activities (§8+§9)

8.1/8.2: listSocialFeed is now a UNION ALL of local and remote
branches, sorted on COALESCE(remote_published_at, created_at):
- local rows: visibility='public' from accepted local follows — and
  the previously missing accepted_at filter is added (the spec's
  'Pending follows contribute nothing' scenario)
- remote rows: gated structurally by joining the viewer's OWN accepted
  follow against the originating actor — which is exactly the
  followers-only audience rule (a row reaches only viewers whose
  follow brought it in); attribution from the remote_actors cache;
  cards link outward to the canonical origin page (no local detail
  page for remote rows)

9.1: annotated — local Pending button shipped with locked accounts;
remote Pending lives on /follows/outgoing.
9.2: already enforced + tested since §3 (actor/webfinger 404).
9.3 hardening: deliver-activity now re-checks the OWNER's profile
visibility at send time, closing the enqueue→delivery flip window
(enqueue-side and inbound-side gates already existed).

Integration tests: the §8 audience-leak guard (A sees followers-only,
B does not), public remote attribution + outward link, pending
contributes nothing, mixed local/remote COALESCE ordering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-06-07 12:13:23 +02:00
parent f8b24f58ef
commit bf2d8bfbd4
6 changed files with 245 additions and 27 deletions

View file

@ -66,14 +66,15 @@
## 8. Feed query update
- [ ] 8.1 Update `listSocialFeed` to include audience-aware filtering: include `(audience='public' OR (audience='followers-only' AND f.followed_actor_iri = a.remote_actor_iri))` predicate
- [ ] 8.2 Update feed sort to use `COALESCE(created_at, remote_created_at)` for mixed local/remote rows
- [x] 8.1 Update `listSocialFeed` to include audience-aware filtering: include `(audience='public' OR (audience='followers-only' AND f.followed_actor_iri = a.remote_actor_iri))` predicate
- [x] 8.2 Update feed sort to use `COALESCE(created_at, remote_created_at)` for mixed local/remote rows
## 9. Profile page updates
- [ ] 9.1 Pending state on Follow button (distinct from Follow/Unfollow)
- [ ] 9.2 Federation gate: actor object endpoint returns 404 if `profile_visibility = 'private'`, mirroring the human profile gate
- [ ] 9.3 When a user flips to `private`, federation must stop: outgoing `Accept(Follow)` rows where they're the followed party are no longer honored, push delivery is suppressed, and inbound Follows return 404. (Existing accepted follow rows on the remote side are not actively reaped — remotes will discover via their next outbox poll returning 404 / via a broadcast `Update` of the actor — pick the right approach during implementation)
- [x] 9.1 Pending state on Follow button (distinct from Follow/Unfollow)
> Shipped with locked accounts for local profiles; remote Pending lives on /follows/outgoing (no local profile page for remote actors).
- [x] 9.2 Federation gate: actor object endpoint returns 404 if `profile_visibility = 'private'`, mirroring the human profile gate
- [x] 9.3 When a user flips to `private`, federation must stop: outgoing `Accept(Follow)` rows where they're the followed party are no longer honored, push delivery is suppressed, and inbound Follows return 404. (Existing accepted follow rows on the remote side are not actively reaped — remotes will discover via their next outbox poll returning 404 / via a broadcast `Update` of the actor — pick the right approach during implementation)
## 10. Privacy + docs