Trust & Content Distribution
How trust relationships let an action spread beyond its immediate recipient, and how a container owner vouches for content it accepted so its members can accept it too.
Two Relay Mechanisms, Not One
Cloudillo has two ways of re-broadcasting someone else’s action, and both of them produce an APRV signed by the receiver. They are easy to confuse and they are not the same thing:
| Wall-post relay | Child relay | |
|---|---|---|
| Trigger | An approvable action addressed to us, from a connected peer, with auto-approve on | An accepted child of a container we own that declares relay_children |
| Mechanism | Auto-approve → APRV → follower broadcast | Owner-vouched APRV → subscriber fan-out |
| Recipients | Our followers | The container’s subscribers |
| APRV shape | aud = original issuer, sub = the action |
no aud, sub = the child, p = the container |
| Typical case | A member posts to a community wall | A member sends MSG into a CONV, or joins it with SUBS |
The rest of this page takes them in turn. Both are implemented in cloudillo-action (post_store.rs, fanout.rs).
Auto-Approval for Trusted Connections
When an action arrives addressed to us and its issuer is trusted, the receiver can approve it automatically and then push the approval — with the content bundled — to its own followers. This is how a post lands on a community wall and reaches the community’s followers.
Auto-Approval Conditions
All of the following must hold (try_auto_approve in post_store.rs):
- Approvable type — the action type declares
approvable=true(POST, MSG, REPOST). - Addressed to us —
action.audience == our_id_tag. - Not our own —
action.issuer != our_id_tag. - Setting enabled —
profile.auto_approve_actionsistrue. - Connected issuer — the issuer’s local profile reports a bidirectional connection (
connected.is_connected()), meaning both sides have sentCONNtokens.
Auto-approval runs only for inbound, non-sync actions.
Auto-Approval Flow
flowchart TD
A[Inbound action stored] --> B{All five<br/>auto-approve<br/>conditions met?}
B -- no --> C[Standard processing<br/>action waits for<br/>manual approval]
B -- yes --> D[Set status to 'A']
D --> E["Mint APRV<br/>aud = original issuer<br/>sub = action_id<br/>visibility = 'F'"]
E --> F{Does the subject type<br/>declare broadcast=true?}
F -- no --> G[Deliver APRV to the<br/>issuer only]
F -- yes --> H[schedule_broadcast_delivery<br/>to every follower]
H --> I["POST /api/inbox<br/>{token: APRV, related: [POST]}"]
The broadcast recipient set is every profile carrying the follower flag, minus the author; Suspended, Blocked and Banned profiles are already dropped by the query (helpers.rs).
Because the APRV carries the approved token as a related action, followers who never had a relationship with the original author still accept the content — the community’s signature vouches for it. See Communities for the same path told from the community’s side.
Child Relay: Owner-Vouched relay_children
A conversation is different. Its members are subscribers of a container, not followers of its owner, and a message from one member has to reach every other member — including members who have no relationship with the sender at all.
Containers that declare relay_children solve this by having the owner vouch for every child it accepts. Today CONV is the only type that declares it, alongside subscribable.
What the Owner Does
When a child action (a MSG, a join SUBS) is accepted on the container owner’s node, maybe_relay_child_to_subscribers (fanout.rs) runs:
- Cheap exit. No parent and no action-typed subject means there is no container to find — the common POST/REACT/FLLW case leaves immediately.
- Skip our own actions. A child issued by us is never vouched. This is the recursion guard: without it the owner’s own vouching APRV would itself be a child of the container and would be vouched again, forever. It also keeps our own
STATout, and leaves the owner’s ownMSG/SUBSun-vouched — those are already accepted by the normal gates. - Resolve the container. Walk the parent chain first (
MSG→CONV), then an action-typed subject (SUBS→CONV). A 1:1 direct message resolves to nothing, so DM delivery stays on its ordinary audience path. - Check the flags. The container’s type must declare
relay_children, and we must actually own the container. - Mint the vouch. An owner-signed
APRVwithsub= the child’s action ID,p= the container’s action ID,visibility = 'F'and no audience — it fans out to subscribers, not to one recipient.
sequenceDiagram
participant B as Bob (member)
participant A as Alice (CONV owner)
participant C as Charlie (member)
B->>A: MSG (parent = CONV)
Note over A: hooks accept the child<br/>resting status 'A'
A->>A: mint APRV<br/>sub = MSG, p = CONV, vis 'F'
Note over A: vouched = true<br/>raw fan-out suppressed
A->>C: {token: APRV, related: [MSG]}
A->>B: {token: APRV, related: [MSG]}
Note over C: R3 accepts the APRV<br/>MSG stored pre-approved
Why the Raw Fan-Out Is Suppressed
process_after_store gates the vouch on the hook’s resting status: only a child that came to rest at 'A' is vouched, so a rejected child (a stranger’s SUBS resting at 'D') is never relayed. If the vouch was minted, schedule_subscriber_fanout is skipped entirely — the APRV now carries the child, and sending the raw token as well would duplicate it.
Note
A hook-rejected child is never vouched. This is what keeps the relay from laundering content the container itself refused.
What the Members Do
Two inbound rules make the receiving side work:
- Rule R3 — accept an
APRVwhose parent is a container we hold that declaresrelay_children, when the APRV’s issuer owns that container. The JWT signature is verified before this point, so the vouch cannot be forged; the bundled related token is bound toAPRV.subjectand re-checked inprocess_related_actions. R3 keys onrelay_children, not on a type list, so it stays generic (process.rs). - Pre-approved
SUBS— a relayedSUBSis accepted on a member’s node even with no localINVTand even though the issuer is not the container’s creator. That is the whole point of the relay: every member learns the full roster. The owner only vouched it after its own hooks accepted it (native_hooks/subs.rs).
Roster Backfill
The vouching APRV pushes a new member forward to everyone already in the container. It does nothing about the other direction — the joiner has never seen the members who were there before.
So on an accepted join SUBS, the owner also runs backfill_roster_to_joiner (fanout.rs): for every existing active member it delivers that member’s vouching APRV bundled with their SUBS, to the joiner only. The joiner ends up with the complete pre-existing roster.
One member has no vouching APRV to deliver: the owner’s own SUBS, which was created locally and therefore never vouched (step 2 above). It is delivered raw and accepted through allow_unknown plus the creator branch in the SUBS hook.
Subscriber Fan-Out
The raw fan-out is still the path for subscribable containers that do not declare relay_children, and for every case where the vouch was not minted.
schedule_subscriber_fanout (fanout.rs) walks the parent chain to a subscribable root, checks that we own it — a remote owner does its own fan-out, we just deliver to it — lists the active ('A') SUBS rows against it, and schedules one delivery per subscriber, excluding ourselves and the sender.
flowchart TD
A[Bob sends MSG on his own node] --> B{Is the CONV local?}
B -- no --> C[Deliver to the CONV owner only]
C --> D[Alice's node receives it at /api/inbox]
D --> E[Alice runs process_after_store]
E --> F{relay_children<br/>container we own?}
F -- yes --> G[Owner-vouched APRV<br/>raw fan-out suppressed]
F -- no --> H[schedule_subscriber_fanout<br/>one delivery per active SUBS]
Related Action Bundling
Both mechanisms lean on the same envelope: an action may be delivered with other tokens attached, so the recipient has everything it needs to accept it in one request.
Delivery Payload
{
"token": "eyJhbGciOi...MAIN_ACTION_TOKEN",
"related": [
"eyJhbGciOi...RELATED_ACTION_TOKEN_1",
"eyJhbGciOi...RELATED_ACTION_TOKEN_2"
]
}Bundling is driven by the deliver_subject behaviour flag. The two live uses are APRV + the approved action, and INVT + the subject CONV.
Processing
flowchart TD
A[Inbox receives token + related] --> B["Store related tokens<br/>status 'W' (waiting)"]
B --> C[Process the main action<br/>verify signature, check<br/>permissions, store, run hook]
C --> D[process_related_actions]
D --> E[For each related token:<br/>verify signature,<br/>skip the relationship gate,<br/>store]
Key point: related actions skip the relationship gate, never the signature check. Trust flows from the APRV’s issuer to the content it vouched for — which is exactly why the vouch is only minted for children the owner’s own hooks accepted.
See Also
- Walkthrough — A Conversation in a Community - Both mechanisms as one worked example
- Communities - What a community is, and the wall-post relay in context
- Action Lifecycle - Where both relays sit in
process_after_store - Approval Token - APRV token details
- Subscription Token - SUBS token details
- Conversation Token - Subscribable conversations
- Action Delivery - Delivery mechanisms
- Security Considerations - Rule R3 and the inbound acceptance gate