WeaveDocs
Woven

Transport

How Woven events move over weave-swarm — topic derivation, envelope framing, deduplication, and the transport service lifecycle.

Purpose

This page covers the network layer for Woven: WovenTransport (topic derivation and frame encoding), WovenTransportService (the stateful receiver that joins spaces, verifies, deduplicates, and persists events into a Strand), and the ordering/idempotency guarantees a peer can rely on. Read this when wiring a weave_swarm::Swarm into an app that publishes or consumes signed events.

Topic derivation

Every SpaceId deterministically maps to a weave_swarm::TopicId via WovenTransport::topic_for_space. Two peers joining the same space land on the same topic without coordination:

use woven::{SpaceId, WovenTransport};

let space = SpaceId::new("woven://dsocial/global")?;
let topic = WovenTransport::topic_for_space(&space);

// stable across processes and machines
assert_eq!(topic, WovenTransport::topic_for_space(&space));

Different spaces always produce different topic IDs, so swarm-level cross-talk between unrelated apps is impossible.

Wire envelope

Outbound events are framed as { protocol: "woven.transport.event", version: 1, space, event } JSON. The receiver rejects unknown protocols, unknown versions, and frames where the envelope space does not match the embedded event.space(), then re-runs full signature verification before accepting the event:

use woven::WovenTransport;

let frame = WovenTransport::encode_event(&event)?;
let decoded = WovenTransport::decode_event(&frame)?;
assert_eq!(decoded.id(), event.id());

Tampering with the body, swapping the public key, or rewriting the space all surface as WovenError::Schema or WovenError::VerificationFailed.

The transport service

WovenTransportService owns the runtime state for an inbound peer: the set of joined spaces, a shared local Strand for persisted events, and an in-memory seen set for idempotent delivery. Join the configured spaces on a swarm and then route swarm events through the service:

use std::sync::Arc;
use tokio::sync::Mutex;
use woven::{SpaceId, WovenTransportService};

let strand = Arc::new(Mutex::new(strand::Strand::new_in_memory()));
let service = WovenTransportService::new(
    [SpaceId::new("woven://dsocial/global")?],
    strand.clone(),
);

service.join_spaces(&swarm).await?;

while let Some(swarm_event) = inbound.recv().await {
    match service.handle_swarm_event(swarm_event).await {
        Ok(Some(record)) => tracing::info!(seq = record.seq, "accepted event"),
        Ok(None) => {} // duplicate or non-Woven swarm event
        Err(e) => tracing::warn!(?e, "rejected payload"),
    }
}

Ordering, retry, and idempotency

  • Per-author ordering: each author's own events are strictly ordered by their Strand sequence; consumers see them in the order the author published.
  • Cross-author ordering: there is no global ordering across writers — use NexusEventFeed if you need a merged time-ordered view.
  • Idempotency: the service deduplicates by deterministic event ID via the seen set, so re-delivery from the swarm (legitimate retries, gossip overlap) produces at most one Strand append per event.
  • Retry: Woven does not retry on its own. Reliability comes from weave_swarm's gossip and the fact that any peer can re-broadcast a stored event later — duplicates collapse on the receiver.
  • Topic mismatch: a payload whose space does not match its swarm topic is rejected with WovenError::Transport and never reaches the Strand.

Broadcasting an event

let space = SpaceId::new("woven://dsocial/global")?;
WovenTransport::join_space(&swarm, &space).await?;
let delivered = WovenTransport::broadcast_event(&swarm, &event).await?;
tracing::info!(delivered, "broadcast complete");

broadcast_event returns the number of currently connected peers that received the frame. Peers that come online later pick up the event through replicated Strands, not retroactive broadcast.

Primary types to know

  • WovenTransport — topic derivation, frame encode/decode, join/broadcast helpers
  • WovenTransportService — stateful receiver: join, verify, deduplicate, persist
  • EventRecord — what the service hands back on a successful append
  • WovenError::Transport — surfaced when a payload is for a space the receiver did not join, or when topic/space disagree