-module(delivery). -export([delivery_set/2, delivery_set/3, collect_recipients/1, suppress_self/2, dedup/1, expand_audience/3]). %% Audience-resolving delivery set computation per design §13.4. %% %% delivery_set/2(Activity, KernelState) returns a sorted, deduped %% list of ActorId atoms — every actor the outgoing Activity needs %% to be POSTed to. Sources: %% - Activity's `:to` field (single ActorId or list) %% - Activity's `:cc` field (single ActorId or list) %% - audience-symbol expansion of `public` and `followers` %% %% Self-delivery (the publishing actor reading their own activity %% on a peer's behalf) is suppressed. %% %% Output for Step 7a is the bare ActorId list; Step 8 will resolve %% each entry to `{PeerInstanceUrl, ActorId}` via the peer-actors %% cache. delivery_set(Activity, KernelState) -> delivery_set(Activity, KernelState, follower_graph:new()). delivery_set(Activity, KernelState, FollowerGraph) -> Self = sender(Activity), Raw = collect_recipients(Activity), Expanded = expand_all(Raw, Self, KernelState, FollowerGraph), Suppressed = suppress_self(Expanded, Self), dedup(Suppressed). %% collect_recipients/1 — flat list from :to + :cc, normalised so %% each element is either an ActorId atom or an audience symbol %% (`public` / `followers`). collect_recipients(Activity) -> To = envelope_field_list(to, Activity), Cc = envelope_field_list(cc, Activity), To ++ Cc. envelope_field_list(Field, Activity) -> case envelope:get_field(Field, Activity) of not_found -> []; {ok, V} when is_list(V) -> V; {ok, V} -> [V] end. %% 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) -> follower_graph:followers(Sender, Graph); expand_audience(followers, Sender, Graph) -> follower_graph:followers(Sender, Graph); expand_audience(X, _Sender, _Graph) -> [X]. expand_all([], _Self, _State, _Graph) -> []; expand_all([X | Rest], Self, State, Graph) -> expand_audience(X, Self, Graph) ++ expand_all(Rest, Self, State, Graph). suppress_self([], _Self) -> []; suppress_self([Self | Rest], Self) -> suppress_self(Rest, Self); suppress_self([X | Rest], Self) -> [X | suppress_self(Rest, Self)]. dedup(L) -> dedup_acc(L, []). dedup_acc([], Acc) -> Acc; dedup_acc([X | Rest], Acc) -> case contains(X, Acc) of true -> dedup_acc(Rest, Acc); false -> dedup_acc(Rest, Acc ++ [X]) end. contains(_, []) -> false; contains(X, [X | _]) -> true; contains(X, [_ | Rest]) -> contains(X, Rest). sender(Activity) -> case envelope:get_field(actor, Activity) of {ok, A} -> A; _ -> nil end.