History Sync

When a new federation link forms, the fresh peer has none of the other side’s earlier content. History sync closes that gap: the receiver pulls a bounded tail of the peer’s recent broadcast actions and feeds them through the ordinary inbound pipeline, so a new follow does not open onto an empty wall.

When It Runs

A HistoryFetchTask is scheduled whenever a federation link is newly established:

  • an FLLW action creating a follow, and
  • a CONN action reaching the Connected state (either side of the handshake).

Scheduling is fire-and-forget — a failure to enqueue is logged and never breaks the action that triggered it. The task is keyed history_sync:{tn_id}:{peer_id_tag}, which collapses the several branches that can fire for one logical connection (a CONN’s on_receive and on_accept, a CONN-ACC followed by an FLLW) into a single fetch.

The Fetch

The task issues a single request to the peer:

GET /api/outbox?since={unix_seconds}&limit={n}

since is derived as now - since_days × 86400. Both bounds come from settings, with defaults applied when unset:

Setting Default Meaning
federation.history_sync.since_days 30 How far back the receiver asks for
federation.history_sync.limit 10 How many primary actions the receiver asks for

A limit of 0 disables history sync for that tenant — the task returns immediately without contacting the peer.

The serving side clamps the request against its own federation.history_sync.limit setting and then against a hard cap of 100 items, regardless of what the caller asked for. The cap bounds both the response size and the per-call signing cost, since each primary is served with a freshly minted STAT.

What the Outbox Returns

The response uses the same envelope as inbox delivery — a primary token plus a related array of dependent tokens:

{
  "items": [
    { "token": "…", "related": ["…"] }
  ]
}

Which action types count as primary is derived from the action DSL rather than hardcoded: every type whose behaviour flags say broadcast = true and not local_only, minus an auxiliary list (APRV, STAT) that only ever rides along with a primary. Today that yields POST and REPOST; a future broadcast content type extends history sync automatically by virtue of its DSL definition.

Auxiliary tokens in related do not count against the item limit.

Items come in two shapes:

  • Case A — the tenant’s own wall post. token is the POST or REPOST; related carries a freshly minted STAT with its current counters.
  • Case B — the audience bridge. A third-party post published to the tenant’s wall. token is the tenant’s most recent active APRV approving that post, and related carries the third-party post itself plus a STAT. The receiver follows the tenant, so the APRV passes its inbound permission check and the bridged post is processed through the pre-approved related-token path — which is how content the receiver has no direct relationship with still arrives legitimately.

Ingestion

For each item the receiver pre-stores every related token as an inbound action linked to the primary, then processes the primary. The primary’s post-store hook drives the related tokens through the standard related-action pipeline — byte for byte the same protocol as /inbox ingestion, not a parallel path.

sequenceDiagram
    participant R as Receiver
    participant P as Peer
    R->>P: GET /api/outbox?since&limit
    P-->>R: items[] (token + related)
    loop each item
        R->>R: pre-store related as inbound actions
        R->>R: process primary token
        R->>R: post-store hook processes related
    end

Re-running a sync is safe. Action IDs are content-addressed (action_id = hash(token)), and the inbound-action insert ignores duplicates on (tenant, action_id), so an overlapping schedule or a scheduler retry silently converges instead of raising conflicts.

Failure Handling

Note

If the peer answers 403 Permission Denied, the task is dropped quietly rather than retried. That response means the peer has not federated back yet — or has revoked the link — and the next federation event will schedule a fresh sync anyway. Network and server errors are distinct: those propagate, and the scheduler retries them under its normal backoff.

An individual item that fails to ingest is logged and skipped; the rest of the batch still lands.

See Also