peers

open override val peers: StateFlow<Set<PeerId>>

Live set of peers currently connected. Includes selfId.

Initial value invariant: The initial value of this StateFlow is { selfId } — this peer is included from the moment a Seam is created, even before any remote peers connect. This makes peers.value.size > 1 a reliable sentinel for "at least one remote peer is connected."

Every subsequent emission reflects the current connected peer set, always including this peer's own id. Remote peers are added when connections complete and removed when connections drop.

Membership, not one-hop reachability

A peer here is one this seam can carry a frame to — not necessarily over a direct link. A Seam may be a view whose route is longer than one hop: a room's channel view publishes the admitted roster, and on a star fabric a co-spoke in that roster is reached by relaying through the host (#1994). So do not read this set as "peers I hold a link to". On a star that is a strictly smaller set, and the gap between the two is what #1994 was: a view published the roster here while routing sendTo through the transport, so it named peers it could not then address.

The obligation the set does carry is that pairing. A peer in peers must be addressable by sendTo — by whatever hop count the implementation uses — so an implementation that publishes a peer it has no route to is the bug, not the caller that believed it.

A Torn seam has no reachable peers — collapse this to { selfId }

Once the seam reaches SeamState.Torn this set must be exactly { selfId }. A torn fabric can reach nobody, so a remote peer left here is a claim sendTo immediately disproves by throwing PeerNotConnected for a peer peers calls reachable. Publish the collapsed roster before, or atomically with, latching Torn, so a consumer woken by the terminal state already observes the collapse. LinkSeam.tearDown collapses then tears; MeshSeam.tearDown does both inside one lock section — either shape is fine.

This is load-bearing, not tidiness. A seam that folds other seamsus.tractat.kuilt.core.composite.CompositeSeam bonding plies — decides which peers are reachable from each member's peers, and its liveness test is only that the member is still attached. A member that latches Torn without collapsing therefore keeps contributing peers to that fold until it is detached, leaving the composite advertising a peer reachable only through a dead transport. Before this was stated, what closed that gap was a convention every in-tree fabric happened to follow rather than a rule an implementor could read (#1816).

Asserted by SeamConformanceSuite.peersCollapseToSelfIdWhenTorn, gated on the collapsesPeersOnTear capability so a fabric that does not honour it yet declares a tracked gap rather than passing silently.

What the TCK can and cannot see. It asserts the terminal value — a Torn seam's peers is { selfId } — and not the ordering, because it must work against a fabric whose seams it drives through a dispatcher: a collector that resumes after close() returns always reads the settled value, so an ordering assertion there would pass for every implementation and prove nothing. That makes the ordering clause above no less binding, only unenforceable from a portable suite. Pin it per fabric with an inline collector, as CompositeCloseCollapseOrderTest does — it reads peers from inside the Torn write itself, and a fabric that latches before collapsing fails it while the TCK obligation stays green.