Summary
Attachment byte delivery is currently direct-peer-only. An attachment can only be delivered to a node that is (a) a direct, mutually-dialed peer of the sender and (b) directly connected to a node that holds the blob. There is no multi-hop delivery and no delivery via a relay peer other than a direct holder — even though the distribution document (CRDT metadata) gossips transitively across the whole mesh.
This is an upstream design limitation in peat-protocol (targeting) and peat-mesh (blob fetch), not a peat-node bug. peat-node only consumes these APIs. Filing here as the consumer-side tracking issue; implementation lands upstream, one PR per repo.
Evidence
Targeting — peat-protocol resolve_targets (storage/file_distribution.rs). Every distribution scope resolves through the sender's known_peers() (direct dials only):
AllNodes → known_peers().fmt_short()
Nodes { node_ids } → requested ids filtered to known_peers
Formation → known_peers today, with a literal // TODO: Query formation membership from Automerge documents
Capable → known_peers
A node reachable only transitively (2+ hops) receives the distribution document (gossip is transitive) but is never in target_nodes, so its receive watcher correctly skips it.
Fetch — peat-mesh fetch_blob (storage/iroh_blob_store.rs). The receiver pulls bytes only from its own known_peers. A blob_peer_index exists but is populated from prior direct fetches, not discovery. With no connected peer it errors ("Remote fetch requires P2P connectivity"). There is no "ask the mesh who has blob X" and no store-and-forward relay. (iroh relay_urls is connection-level NAT traversal, not application-level blob multi-hop — the candidate provider set is still known_peers.)
Impact
- Operators must wire full bidirectional dials between every sender/receiver pair for attachments to work. Across NAT this silently breaks when only one dial direction succeeds: the document syncs over the working direction, but
target_nodes (or the blob fetch) excludes the unreachable side, so the file never lands.
- Defeats the DDIL premise that a file written on one node should propagate through the mesh via whatever connectivity exists — not just to its direct neighbors.
Proposed direction (upstream)
peat-protocol — membership-based targeting. Resolve target_nodes against the gossiped / Automerge mesh-membership set, not the sender's direct known_peers. The existing Formation TODO is the natural seam.
peat-mesh — provider discovery and/or store-and-forward. Either query the mesh for blob providers ("who has blob X?") or have intermediary peers cache and re-provide, so any connected peer holding the blob can serve it — not only a direct holder.
Per-repo work
Related
Summary
Attachment byte delivery is currently direct-peer-only. An attachment can only be delivered to a node that is (a) a direct, mutually-dialed peer of the sender and (b) directly connected to a node that holds the blob. There is no multi-hop delivery and no delivery via a relay peer other than a direct holder — even though the distribution document (CRDT metadata) gossips transitively across the whole mesh.
This is an upstream design limitation in
peat-protocol(targeting) andpeat-mesh(blob fetch), not apeat-nodebug.peat-nodeonly consumes these APIs. Filing here as the consumer-side tracking issue; implementation lands upstream, one PR per repo.Evidence
Targeting —
peat-protocolresolve_targets(storage/file_distribution.rs). Every distribution scope resolves through the sender'sknown_peers()(direct dials only):AllNodes→known_peers().fmt_short()Nodes { node_ids }→ requested ids filtered toknown_peersFormation→known_peerstoday, with a literal// TODO: Query formation membership from Automerge documentsCapable→known_peersA node reachable only transitively (2+ hops) receives the distribution document (gossip is transitive) but is never in
target_nodes, so its receive watcher correctly skips it.Fetch —
peat-meshfetch_blob(storage/iroh_blob_store.rs). The receiver pulls bytes only from its ownknown_peers. Ablob_peer_indexexists but is populated from prior direct fetches, not discovery. With no connected peer it errors ("Remote fetch requires P2P connectivity"). There is no "ask the mesh who has blob X" and no store-and-forward relay. (irohrelay_urlsis connection-level NAT traversal, not application-level blob multi-hop — the candidate provider set is stillknown_peers.)Impact
target_nodes(or the blob fetch) excludes the unreachable side, so the file never lands.Proposed direction (upstream)
peat-protocol— membership-based targeting. Resolvetarget_nodesagainst the gossiped / Automerge mesh-membership set, not the sender's directknown_peers. The existingFormationTODO is the natural seam.peat-mesh— provider discovery and/or store-and-forward. Either query the mesh for blob providers ("who has blob X?") or have intermediary peers cache and re-provide, so any connected peer holding the blob can serve it — not only a direct holder.Per-repo work
peat-protocol: membership-basedresolve_targetspeat-mesh: blob provider discovery / store-and-forward relaypeat-node: integration + a multi-hop (3-node, non-adjacent sender/receiver) delivery functest once the upstream pieces landRelated