GossipSeam

class GossipSeam(base: Seam, random: Random, clock: () -> Instant, config: HeartbeatConfig = HeartbeatConfig(), spareCount: Int = GossipView.DEFAULT_SPARE_COUNT, jitter: ClosedRange<Duration> = GossipView.DEFAULT_JITTER, initialTtl: Int = DEFAULT_TTL, topology: TopologyPolicy = RandomKRegular(random), policy: DeliveryPolicy = DeliveryPolicy.Reliable, reorderGrace: Duration = DEFAULT_REORDER_GRACE) : Seam, PrincipalRoster

A partial-mesh Seam over a base full-membership seam, exposing two views of the endpoints (docs/gossip-mesh-design.md):

  • active-neighbour view (activePeers) — the ~k peers this node pushes deltas to and GCs against. Shaped by the injected TopologyPolicy and maintained by an internal GossipView.

  • full-membership view (peers) — everyone in the room, the pool anti-entropy samples. Delegated straight from base.

For a full-mesh base seam the active view is a strict subset, so broadcast floods only to the ~k active neighbours rather than the whole room — the O(N)-fan-out win.

Relayed dissemination (Phase 3). broadcast wraps the payload in a GossipFrame (origin id + per-origin sequence + a hop-budget TTL) and eager-floods it to the active neighbours. On receive, incoming decodes the frame, delivers the payload to the application once and in per-origin send order — keyed by the (origin, seq) pair in a bounded GossipDedup, which holds reordered frames for contiguous release — and, while the TTL permits, decrements the budget and re-floods to this node's active neighbours minus the peer the frame arrived from. So a broadcast reaches the whole overlay device-to-device along ~k-regular edges, dedup terminates the flood (a node relays each message at most once), and the TTL is only a hard cap against pathological loops. Anything a flood drops is backstopped by anti-entropy (Phase 1), so the overlay need only be usually connected. sendTo is delegated straight to base (point-to-point, unwrapped), which on a full-mesh transport can reach any connected peer directly.

Single-collection incoming (ADR-034). GossipSeam is the single collector of base.incoming. Its start loop fans every inbound Swatch to an internal rawIncoming bus that the per-neighbour detectors subscribe to, and re-publishes only non-heartbeat frames to incoming — ping/pong frames are consumed by the detectors and never surface to the application. Collect incoming exactly once; wrap with shareIn for fan-out.

Reverse-edge liveness. The active view is directed (an independent per-peer k-out sample), so a peer may watch this node without being watched back. An inbound ping from such a peer is answered with a pong directly (see answerUnwatchedPing) — otherwise the watcher's detector would starve and tear every asymmetric edge down, collapsing the overlay to mutual-only edges.

Lifecycle. Call start once with a scope you own; it launches the inbound loop and the GossipView. All timing/scheduling runs on that scope, all randomness on the injected seeded random, time via the injected clock.

Parameters

base

the underlying full-membership seam.

random

seeded RNG, seeded per-peer by the caller (drives view-recompute jitter; with the default topology it also seeds neighbour selection).

clock

injected time source for the per-neighbour detectors; never the wall clock.

topology

the overlay shape — which peers this node eager-floods to (see TopologyPolicy). Defaults to the RandomKRegular partial mesh seeded from random; pass FullFanout for a hub star. Only broadcast dissemination is shaped; sendTo always passes through unwrapped.

jitter

per-peer view-recompute jitter window (see GossipView); a zero range makes recompute synchronous, which deterministic tests rely on.

initialTtl

hop budget stamped on a locally-originated broadcast. Dedup is what terminates the flood; this is only a generous hard cap, comfortably above the overlay diameter at the tens–low-hundreds target scale.

reorderGrace

how long a relayed frame held for an earlier same-origin gap waits before the gap is abandoned and the held run released in order (see GossipDedup). Multi-path relay reordering resolves within a few hops' latency, so a gap older than this is a genuine flood drop (anti-entropy backstops it) or a pre-join seq (a late joiner first sights an origin mid-stream) — either way the held frames must not wait forever. Measured on the seam's own sweep ticker (dispatcher time — virtual under a test dispatcher), never on clock, which is the liveness time source and may be frozen (#1309).

Constructors

Link copied to clipboard
constructor(base: Seam, random: Random, clock: () -> Instant, config: HeartbeatConfig = HeartbeatConfig(), spareCount: Int = GossipView.DEFAULT_SPARE_COUNT, jitter: ClosedRange<Duration> = GossipView.DEFAULT_JITTER, initialTtl: Int = DEFAULT_TTL, topology: TopologyPolicy = RandomKRegular(random), policy: DeliveryPolicy = DeliveryPolicy.Reliable, reorderGrace: Duration = DEFAULT_REORDER_GRACE)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val activePeers: StateFlow<Set<PeerId>>

The active-neighbour view — deltas/GC target set. Strict subset of peers.

Link copied to clipboard
open override val attestedPrincipals: StateFlow<Map<PeerId, Principal>>

Host-verified principals of the base seam's linked peers, delegated to base when it is a PrincipalRoster (a hub mesh with attested links); a constant empty map otherwise.

Link copied to clipboard
open val capability: StateFlow<TransportCapability>
Link copied to clipboard
open override val incoming: Flow<Swatch>

Application frames only — heartbeat ping/pong frames are filtered out.

Link copied to clipboard
open val maxPayloadBytes: Int?
Link copied to clipboard
open override val peers: StateFlow<Set<PeerId>>

Full-membership view (includes selfId); the anti-entropy sampling pool.

Link copied to clipboard
open val plies: StateFlow<Map<PlyId, SeamState>>
Link copied to clipboard
open override val selfId: PeerId
Link copied to clipboard
val spares: StateFlow<List<PeerId>>

Ordered standby neighbours promoted on active-neighbour loss.

Link copied to clipboard
open override val state: StateFlow<SeamState>

Functions

Link copied to clipboard
open suspend override fun broadcast(payload: ByteArray)

Eager-flood to the active neighbours only. A defined no-op when the active view is empty (alone in the session, or the view has not reconciled yet), matching the Seam broadcast contract.

Link copied to clipboard
open suspend override fun close(reason: CloseReason)
Link copied to clipboard
open suspend override fun sendTo(peer: PeerId, payload: ByteArray)
Link copied to clipboard
fun start(scope: CoroutineScope)

Starts the inbound event loop and the GossipView. Idempotent only if called once per scope; call exactly once.