diff --git a/next/kernel/delivery.erl b/next/kernel/delivery.erl index ff16c6a8..f7480d68 100644 --- a/next/kernel/delivery.erl +++ b/next/kernel/delivery.erl @@ -45,13 +45,15 @@ envelope_field_list(Field, Activity) -> {ok, V} -> [V] end. -%% expand_audience/3 — Step 7b. `followers` -> the sender's -%% followers proplist entry from a follower_graph state. -%% `public` for v2 expands to []. Step 7c layers a peer-instance -%% known-set on top for real Public delivery. Other symbols / -%% explicit ActorIds pass through unchanged. +%% expand_audience/3 — `followers` -> the sender's followers +%% proplist entry from a follower_graph state. `public` for v2 +%% expands to the same list (per design §13.4: practical Public +%% fan-out is "every follower of the publishing actor"). The +%% explicit shared-inbox peer-instance model defers to v3. +%% Other symbols / explicit ActorIds pass through unchanged. -expand_audience(public, _Sender, _Graph) -> []; +expand_audience(public, Sender, Graph) -> + follower_graph:followers(Sender, Graph); expand_audience(followers, Sender, Graph) -> follower_graph:followers(Sender, Graph); expand_audience(X, _Sender, _Graph) -> [X]. diff --git a/next/tests/delivery_set.sh b/next/tests/delivery_set.sh index 7bfc94de..9165fdba 100755 --- a/next/tests/delivery_set.sh +++ b/next/tests/delivery_set.sh @@ -76,9 +76,17 @@ cat > "$TMPFILE" <<'EPOCHS' (epoch 18) (eval "(get (erlang-eval-ast \"delivery:delivery_set([{actor, alice}, {to, followers}], [], follower_graph:new()) =:= []\") :name)") -;; public audience symbol -> [] for v2 (Step 7c will populate) +;; public audience symbol -> sender's followers for v2 (§13.4) (epoch 19) -(eval "(get (erlang-eval-ast \"delivery:delivery_set([{actor, alice}, {to, public}], []) =:= []\") :name)") +(eval "(get (erlang-eval-ast \"F = [{actor, bob}, {type, follow}, {object, alice}], A = [{actor, alice}, {type, accept}, {object, F}], S = follower_graph:fold(A, follower_graph:fold(F, follower_graph:new())), delivery:delivery_set([{actor, alice}, {to, public}], [], S) =:= [bob]\") :name)") + +;; public with empty follower-graph -> [] +(epoch 28) +(eval "(get (erlang-eval-ast \"delivery:delivery_set([{actor, alice}, {to, public}], [], follower_graph:new()) =:= []\") :name)") + +;; public + followers in same audience deduped (both expand identically) +(epoch 29) +(eval "(get (erlang-eval-ast \"F = [{actor, bob}, {type, follow}, {object, alice}], A = [{actor, alice}, {type, accept}, {object, F}], S = follower_graph:fold(A, follower_graph:fold(F, follower_graph:new())), delivery:delivery_set([{actor, alice}, {to, [public, followers]}], [], S) =:= [bob]\") :name)") ;; Mixed explicit + followers, followers carry two peers (epoch 20) @@ -136,7 +144,9 @@ check 15 "duplicates within :to deduped" "true" check 16 ":to/:cc overlap deduped" "true" check 17 "followers expands via graph" "true" check 18 "empty follower-graph -> []" "true" -check 19 "public v2 -> []" "true" +check 19 "public -> sender's followers" "true" +check 28 "public empty graph -> []" "true" +check 29 "public + followers dedupe" "true" check 20 "mixed explicit + followers" "true" check 21 "followers + overlap deduped" "true" check 22 "collect_recipients raw flat" "true" diff --git a/plans/fed-sx-milestone-2.md b/plans/fed-sx-milestone-2.md index e205f695..9008c263 100644 --- a/plans/fed-sx-milestone-2.md +++ b/plans/fed-sx-milestone-2.md @@ -493,11 +493,16 @@ expansion via the audience predicates from M1's genesis bundle. suppress_self + dedup helpers + expand_audience pass-through. Module lives in `next/kernel/delivery.erl` (separate from outbox so Step 8's delivery-queue gen_server has a clean home). -- [ ] **7b** — Public expansion: when Cfg or KernelState carries - a known-peer-instance set, `public` expands to one entry per - peer instance for the public-reach broadcast (Mastodon's - shared inbox per-instance pattern). v2 ships the empty case - via 7a so callers don't have to special-case the symbol. +- [x] **7b** — Public audience expansion. v2 default: `public` + expands to the sender's followers (same as `followers`) per + design §13.4 — the practical fan-out for an open social + network is "every follower of the publishing actor". The + explicit shared-inbox peer-instance model (Mastodon-style + per-instance broadcast) defers to v3 when there's a real + known-peer-instance registry to drive it. `public + followers` + in the same audience deduplicates because both symbols + expand identically. 19/19 in `delivery_set.sh` (2 new cases + + 1 case updated from the v2 placeholder behavior). - [ ] **7c** — Outbox-side integration: `outbox:publish/2` computes the delivery set after sign + log and stashes it in the Result proplist as `{delivery_set, [ActorId, ...]}`. Step @@ -845,6 +850,16 @@ proceed. Newest first. +- **2026-06-06** — Step 7b: public audience expansion. + `delivery:expand_audience(public, Sender, Graph)` now returns + the sender's followers (same as `followers`) — per design + §13.4 that's the practical fan-out semantics for an open + social network. The explicit shared-inbox peer-instance model + defers to v3. 19/19 in `delivery_set.sh` (+2 new cases: + public-with-empty-graph, public+followers-dedupe; +1 case + updated from the v2 placeholder). Conformance 761/761 + preserved. + - **2026-06-06** — Step 7a: audience-resolving delivery set. New `next/kernel/delivery.erl`: `delivery_set/2,3(Activity, KernelState[, FollowerGraph])` returns a deduplicated list of